Skip to content

Commit

Permalink
wdb: add median time past.
Browse files Browse the repository at this point in the history
  • Loading branch information
nodech committed Mar 18, 2024
1 parent 7200b63 commit 64a2491
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 3 deletions.
114 changes: 112 additions & 2 deletions lib/wallet/walletdb.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const {BloomFilter} = require('@handshake-org/bfilter');
const {Lock, MapLock} = require('bmutex');
const bdb = require('bdb');
const Logger = require('blgr');
const LRU = require('blru');
const {safeEqual} = require('bcrypto/lib/safe');
const aes = require('bcrypto/lib/aes');
const Network = require('../protocol/network');
Expand Down Expand Up @@ -71,6 +72,9 @@ class WalletDB extends EventEmitter {
this.state = new ChainState();
this.height = 0;

// block time cache by height.
this.timeCache = new LRU(30);

// wallets
this.primary = null;
this.wallets = new Map();
Expand Down Expand Up @@ -340,6 +344,7 @@ class WalletDB extends EventEmitter {
this.unregister(wallet);
}

this.timeCache.reset();
await this.db.close();
this.logger.info('WalletDB Closed.');
this.emit('close');
Expand Down Expand Up @@ -2247,6 +2252,83 @@ class WalletDB extends EventEmitter {
return block.hash;
}

/**
* Get block time.
* @param {Number} height
* @returns {Promise<Number?>}
*/

async getBlockTime(height) {
assert(typeof height === 'number');

if (height < 0)
return null;

const cache = this.timeCache.get(height);

if (cache != null)
return cache;

const block = await this.getBlock(height);

if (!block)
return null;

this.timeCache.set(height, block.time);

return block.time;
}

/**
* Calculate median time past.
* @param {Number} height
* @returns {Promise<Number>}
*/

async getMedianTime(height) {
assert(typeof height === 'number');
const timespan = consensus.MEDIAN_TIMESPAN;
const median = [];

let time = await this.getBlockTime(height);

for (let i = 0; i < timespan && time; i++) {
median.push(time);

time = await this.getBlockTime(height - i - 1);
}

median.sort(cmp);

return median[median.length >>> 1];
}

/**
* Median time as if the actively adding block is the tip.
* @param {Number} prevHeight
* @param {Number} tipTime
*/

async getMedianTimeTip(prevHeight, tipTime) {
assert(typeof prevHeight === 'number');
assert(typeof tipTime === 'number');

const timespan = consensus.MEDIAN_TIMESPAN;
const median = [];

let time = tipTime;

for (let i = 0; i < timespan && time; i++) {
median.push(time);

time = await this.getBlockTime(prevHeight - i);
}

median.sort(cmp);

return median[median.length >>> 1];
}

/**
* Sync with chain height.
* @param {Number} height
Expand Down Expand Up @@ -2290,6 +2372,7 @@ class WalletDB extends EventEmitter {
let total = 0;

await iter.each(async (key, value) => {
this.timeCache.start();
const [height] = layout.b.decode(key);
const block = MapRecord.decode(value);
this.logger.info('Reverting block: %d', height);
Expand All @@ -2299,6 +2382,8 @@ class WalletDB extends EventEmitter {
assert(wallet);
total += await wallet.revert(height);
}
this.timeCache.unpush(height);
this.timeCache.commit();
});

this.logger.info('Rolled back %d WalletDB transactions.', total);
Expand All @@ -2314,8 +2399,15 @@ class WalletDB extends EventEmitter {
async addBlock(entry, txs) {
const unlock = await this.txLock.lock();

this.timeCache.start();

try {
return await this._addBlock(entry, txs);
const result = await this._addBlock(entry, txs);
this.timeCache.commit();
return result;
} catch (e) {
this.timeCache.drop();
throw e;
} finally {
unlock();
}
Expand Down Expand Up @@ -2355,6 +2447,8 @@ class WalletDB extends EventEmitter {
return 0;
}

this.timeCache.push(tip.height, tip.time);

const walletTxs = [];

try {
Expand Down Expand Up @@ -2397,7 +2491,13 @@ class WalletDB extends EventEmitter {
async removeBlock(entry) {
const unlock = await this.txLock.lock();
try {
return await this._removeBlock(entry);
this.timeCache.start();
const result = await this._removeBlock(entry);
this.timeCache.commit();
return result;
} catch (e) {
this.timeCache.drop();
throw e;
} finally {
unlock();
}
Expand Down Expand Up @@ -2429,6 +2529,8 @@ class WalletDB extends EventEmitter {
const prev = await this.getBlock(tip.height - 1);
assert(prev);

this.timeCache.unpush(tip.height);

// Get the map of block->wids.
const map = await this.getBlockMap(tip.height);

Expand Down Expand Up @@ -2476,9 +2578,13 @@ class WalletDB extends EventEmitter {
return;
}

this.timeCache.start();

try {
await this._addBlock(entry, txs);
this.timeCache.commit();
} catch (e) {
this.timeCache.drop();
this.emit('error', e);
throw e;
}
Expand Down Expand Up @@ -2745,6 +2851,10 @@ function toString(buf) {
return buf.toString('ascii', 1, buf.length);
}

function cmp(a, b) {
return a - b;
}

/*
* Expose
*/
Expand Down
46 changes: 45 additions & 1 deletion test/wallet-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2831,9 +2831,14 @@ describe('Wallet', function() {
await node.close();
});

async function mineBlock(tip) {
async function createBlock(tip) {
const job = await miner.createJob(tip);
const block = await job.mineAsync();
return block;
}

async function mineBlock(tip) {
const block = await createBlock(tip);
return chain.add(block);
}

Expand Down Expand Up @@ -2941,6 +2946,45 @@ describe('Wallet', function() {
await wclient.close();
}
});

it('should get same mtp for chain and wallet', async () => {
const assertSameMTP = async (mtp) => {
assert.strictEqual(wdb.state.height, chain.tip.height);

const chainMTP = await node.chain.getMedianTime(chain.tip);
const walletMTP = await wdb.getMedianTime(wdb.state.height);

assert.strictEqual(walletMTP, chainMTP);
if (mtp)
assert.strictEqual(mtp, chainMTP);
};

await assertSameMTP();

const times = [];
const mtp = await node.chain.getMedianTime(chain.tip);
times[chain.tip.height] = mtp;
for (let i = 0; i < 40; i++) {
const block = await createBlock(chain.tip);
const futureMTP = await wdb.getMedianTimeTip(chain.tip.height, block.time);
await chain.add(block);
await assertSameMTP(futureMTP);
const mtp = await node.chain.getMedianTime(chain.tip);
times[chain.tip.height] = mtp;
}

// revert all
for (let i = 0; i < 40; i++) {
const entry = chain.tip;
const mtp = await chain.getMedianTime(entry);
const tipMtp = await wdb.getMedianTimeTip(entry.height - 1, entry.time);
await assertSameMTP(times[entry.height]);
assert.strictEqual(tipMtp, times[entry.height]);
assert.strictEqual(tipMtp, mtp);

await chain.disconnect(entry);
}
});
});

describe('Wallet Name Claims', function() {
Expand Down

0 comments on commit 64a2491

Please sign in to comment.