Skip to content

Commit

Permalink
Transaction Database (#235)
Browse files Browse the repository at this point in the history
* Transaction DB

* actually await while saving

* Don't save all txs again once wallet is loaded

* readd white space
  • Loading branch information
panleone authored Oct 23, 2023
1 parent fb38a47 commit 6ad0804
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 14 deletions.
77 changes: 76 additions & 1 deletion scripts/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import {
import { PromoWallet } from './promos.js';
import { ALERTS, translation } from './i18n.js';
import { Account } from './accounts.js';
import { COutpoint, CTxIn, CTxOut, Transaction } from './mempool.js';

/** The current version of the DB - increasing this will prompt the Upgrade process for clients with an older version */
export const DB_VERSION = 2;
export const DB_VERSION = 3;

/**
*
Expand Down Expand Up @@ -64,6 +65,28 @@ export class Database {
await store.delete('masternode');
}

/**
* Store a tx inside the database
* @param {Transaction} tx
*/
async storeTx(tx) {
const store = this.#db
.transaction('txs', 'readwrite')
.objectStore('txs');
await store.put(tx, tx.txid);
}

/**
* Remove a tx from the database
* @param {String} txid - transaction id
*/
async removeTx(txid) {
const store = this.#db
.transaction('txs', 'readwrite')
.objectStore('txs');
await store.delete(txid);
}

/**
* Add Promo Code to the database for tracking and management
* @param {PromoWallet} promo
Expand Down Expand Up @@ -292,6 +315,55 @@ export class Database {
return (await store.getAll()).map((promo) => new PromoWallet(promo));
}

/**
* Get all txs from the database
* @returns {Promise<Transaction>}
*/
async getTxs() {
const store = this.#db
.transaction('txs', 'readonly')
.objectStore('txs');
return (await store.getAll()).map((tx) => {
const vin = tx.vin.map(
(x) =>
new CTxIn({
outpoint: new COutpoint({
txid: x.outpoint.txid,
n: x.outpoint.n,
}),
scriptSig: x.scriptSig,
})
);
const vout = tx.vout.map(
(x) =>
new CTxOut({
outpoint: new COutpoint({
txid: x.outpoint.txid,
n: x.outpoint.n,
}),
script: x.script,
value: x.value,
})
);
return new Transaction({
txid: tx.txid,
blockHeight: tx.blockHeight,
blockTime: tx.blockTime,
vin: vin,
vout: vout,
});
});
}
/**
* Remove all txs from db
*/
async removeAllTxs() {
const store = this.#db
.transaction('txs', 'readwrite')
.objectStore('txs');
await store.clear();
}

/**
* @returns {Promise<Settings>}
*/
Expand Down Expand Up @@ -396,6 +468,9 @@ export class Database {
if (oldVersion <= 1) {
db.createObjectStore('promos');
}
if (oldVersion <= 2) {
db.createObjectStore('txs');
}
},
blocking: () => {
// Another instance is waiting to upgrade, and we're preventing it
Expand Down
4 changes: 4 additions & 0 deletions scripts/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,10 @@ export async function start() {
await accessOrImportWallet();
}
} else {
// Clear the transaction DB
const database = await Database.getInstance();
await database.removeAllTxs();

// Just load the block count, for use in non-wallet areas
getNetwork().getBlockCount();
}
Expand Down
66 changes: 61 additions & 5 deletions scripts/mempool.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { getNetwork } from './network.js';
import {
activityDashboard,
getStakingBalance,
stakingDashboard,
} from './global.js';
import { getStakingBalance } from './global.js';
import { Database } from './database.js';
import { getEventEmitter } from './event_bus.js';
import Multimap from 'multimap';
import { wallet } from './wallet.js';
Expand Down Expand Up @@ -180,6 +177,11 @@ export class Mempool {
* @type {number} - Our Cold Staking balance in Satoshis
*/
#coldBalance = 0;
/**
* @type {number} - Highest block height saved on disk
*/
#highestSavedHeight = 0;

constructor() {
/**
* Multimap txid -> spent Coutpoint
Expand All @@ -202,6 +204,8 @@ export class Mempool {
this.txmap = new Map();
this.spent = new Multimap();
this.orderedTxmap = new Multimap();
this.#balance = 0;
this.#coldBalance = 0;
}
get balance() {
return this.#balance;
Expand Down Expand Up @@ -365,4 +369,56 @@ export class Mempool {
getEventEmitter().emit('balance-update');
getStakingBalance(true);
}

/**
* Save txs on database
*/
async saveOnDisk() {
const nBlockHeights = Array.from(this.orderedTxmap.keys())
.sort((a, b) => a - b)
.reverse();
if (nBlockHeights.length == 0) {
return;
}
const database = await Database.getInstance();
for (const nHeight of nBlockHeights) {
if (this.#highestSavedHeight > nHeight) {
break;
}
await Promise.all(
this.orderedTxmap.get(nHeight).map(async function (tx) {
await database.storeTx(tx);
})
);
}
this.#highestSavedHeight = nBlockHeights[0];
}
/**
* Load txs from database
* @returns {Promise<Boolean>} true if database was non-empty and transaction are loaded successfully
*/
async loadFromDisk() {
const database = await Database.getInstance();
const txs = await database.getTxs();
if (txs.length == 0) {
return false;
}
for (const tx of txs) {
this.addToOrderedTxMap(tx);
}
const nBlockHeights = Array.from(this.orderedTxmap.keys()).sort(
(a, b) => a - b
);
for (const nHeight of nBlockHeights) {
for (const tx of this.orderedTxmap.get(nHeight)) {
this.updateMempool(tx);
}
}
const cNet = getNetwork();
cNet.fullSynced = true;
cNet.lastBlockSynced = nBlockHeights.at(-1);
this.#highestSavedHeight = nBlockHeights.at(-1);
this.setBalance();
return true;
}
}
5 changes: 1 addition & 4 deletions scripts/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ export class ExplorerNetwork extends Network {
mempool.updateMempool(mempool.parseTransaction(tx));
}
}
await mempool.saveOnDisk();
}
if (debug) {
console.log(
Expand All @@ -274,17 +275,13 @@ export class ExplorerNetwork extends Network {
async walletFullSync() {
if (this.fullSynced) return;
if (!this.wallet || !this.wallet.isLoaded()) return;
getEventEmitter().emit('sync-status', 'start');
await this.getLatestTxs(0);
const nBlockHeights = Array.from(mempool.orderedTxmap.keys());
this.lastBlockSynced =
nBlockHeights.length == 0
? 0
: nBlockHeights.sort((a, b) => a - b).at(-1);
this.fullSynced = true;
await activityDashboard.update(50);
await stakingDashboard.update(50);
getEventEmitter().emit('sync-status', 'stop');
}

/**
Expand Down
4 changes: 4 additions & 0 deletions scripts/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,10 @@ export async function toggleTestnet() {
// Nuke the Master Key
wallet.setMasterKey(null);

// Clear the transaction DB
const database = await Database.getInstance();
await database.removeAllTxs();

// Hide all Dashboard info, kick the user back to the "Getting Started" area
doms.domGenKeyWarning.style.display = 'none';
doms.domGuiWallet.style.display = 'none';
Expand Down
23 changes: 19 additions & 4 deletions scripts/wallet.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { parseWIF } from './encoding.js';
import { generateMnemonic, mnemonicToSeed, validateMnemonic } from 'bip39';
import { doms, beforeUnloadListener } from './global.js';
import {
doms,
beforeUnloadListener,
activityDashboard,
stakingDashboard,
} from './global.js';
import { getNetwork } from './network.js';
import { MAX_ACCOUNT_GAP } from './chain_params.js';
import { Transaction, HistoricalTx, HistoricalTxType } from './mempool.js';
import {
Transaction,
HistoricalTx,
HistoricalTxType,
CTxOut,
} from './mempool.js';
import {
LegacyMasterKey,
HdMasterKey,
Expand Down Expand Up @@ -785,15 +795,20 @@ export async function importWallet({
// Hide the encryption UI
doms.domGenKeyWarning.style.display = 'none';
}

// Hide all wallet starter options
setDisplayForAllWalletOptions('none');
getEventEmitter().emit('wallet-import');

// Fetch state from explorer, if this import was post-startup
if (getNetwork().enabled) {
getEventEmitter().emit('sync-status', 'start');
if (!(await mempool.loadFromDisk()) && getNetwork().enabled) {
createAlert('info', translation.syncStatusStarting, 12500);
await getNetwork().walletFullSync();
}
await activityDashboard.update(50);
await stakingDashboard.update(50);
getEventEmitter().emit('sync-status', 'stop');

if (getNetwork().enabled && !fStartup) {
refreshChainData();
}
Expand Down

0 comments on commit 6ad0804

Please sign in to comment.