walletdb: Write time with blockmeta record. Update wdb version to 3.
This requires full wdb block entry wipe and rescan. That is handled by PR #889. `layout.h` is looked up by height, so only missing data was time. Now we can implement walletdb only median time past calculation.
This commit is contained in:
parent
c38e4b9298
commit
f233b76a96
7 changed files with 142 additions and 48 deletions
|
|
@ -64,6 +64,18 @@ class WalletClient extends NodeClient {
|
|||
return parseEntry(await super.getEntry(block));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entries.
|
||||
* @param {Number} [start=-1]
|
||||
* @param {Number} [end=-1]
|
||||
* @returns {Promise<Object[]>}
|
||||
*/
|
||||
|
||||
async getEntries(start, end) {
|
||||
const entries = await super.getEntries(start, end);
|
||||
return entries.map(parseEntry);
|
||||
}
|
||||
|
||||
async send(tx) {
|
||||
return super.send(tx.encode());
|
||||
}
|
||||
|
|
@ -138,6 +150,7 @@ function parseEntry(data) {
|
|||
|
||||
const hash = data.slice(0, 32);
|
||||
const height = encoding.readU32(data, 32);
|
||||
// skip nonce 4.
|
||||
const time = encoding.readU64(data, 40);
|
||||
const prevBlock = data.slice(48, 80);
|
||||
|
||||
|
|
|
|||
|
|
@ -268,6 +268,17 @@ class NodeClient extends AsyncEmitter {
|
|||
return this.node.chain.getHashes(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entries range.
|
||||
* @param {Number} start
|
||||
* @param {Number} end
|
||||
* @returns {Promise<ChainEntry[]>}
|
||||
*/
|
||||
|
||||
async getEntries(start = -1, end = -1) {
|
||||
return this.node.chain.getEntries(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rescan for any missed transactions.
|
||||
* @param {Number|Hash} start - Start block.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
const assert = require('bsert');
|
||||
const EventEmitter = require('events');
|
||||
const NameState = require('../covenants/namestate');
|
||||
const Block = require('../primitives/block');
|
||||
const util = require('../utils/util');
|
||||
|
||||
/**
|
||||
|
|
@ -173,6 +174,23 @@ class NullClient extends EventEmitter {
|
|||
return [this.network.genesis.hash];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entries.
|
||||
* @param {Number} [start=-1]
|
||||
* @param {Number} [end=-1]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async getEntries(start = -1, end = -1) {
|
||||
const genesisBlock = Block.decode(this.network.genesisBlock);
|
||||
const entry = {
|
||||
hash: genesisBlock.hash(),
|
||||
height: 0,
|
||||
time: genesisBlock.time
|
||||
};
|
||||
return [entry];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rescan for any missed transactions.
|
||||
* @param {Number|Hash} start - Start block.
|
||||
|
|
|
|||
|
|
@ -118,12 +118,40 @@ class BlockMeta extends bio.Struct {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get block meta hash as a buffer.
|
||||
* Encode hash and time.
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
|
||||
toHash() {
|
||||
return this.hash;
|
||||
toHashAndTime() {
|
||||
const data = Buffer.allocUnsafe(32 + 8);
|
||||
bio.writeBytes(data, this.hash, 0);
|
||||
bio.writeU64(data, this.time, 32);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode hash and time.
|
||||
* @param {Buffer} data
|
||||
* @param {Number} height
|
||||
* @returns {BlockMeta}
|
||||
*/
|
||||
|
||||
fromHashAndTime(data, height) {
|
||||
this.hash = data.slice(0, 32);
|
||||
this.time = bio.readU64(data, 32);
|
||||
this.height = height;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate block meta from hash and time.
|
||||
* @param {Buffer} data
|
||||
* @param {Number} height
|
||||
* @returns {BlockMeta}
|
||||
*/
|
||||
|
||||
static fromHashAndTime(data, height) {
|
||||
return new this().fromHashAndTime(data, height);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -139,19 +167,6 @@ class BlockMeta extends bio.Struct {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate block meta from serialized tip data.
|
||||
* @private
|
||||
* @param {Buffer} data
|
||||
*/
|
||||
|
||||
read(br) {
|
||||
this.hash = br.readHash();
|
||||
this.height = br.readU32();
|
||||
this.time = br.readU32();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate block meta from chain entry.
|
||||
* @param {ChainEntry} entry
|
||||
|
|
@ -162,13 +177,26 @@ class BlockMeta extends bio.Struct {
|
|||
return new this().fromEntry(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate block meta from serialized tip data.
|
||||
* @private
|
||||
* @param {Buffer} data
|
||||
*/
|
||||
|
||||
read(br) {
|
||||
this.hash = br.readHash();
|
||||
this.height = br.readU32();
|
||||
this.time = br.readU64();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate size.
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
getSize() {
|
||||
return 40;
|
||||
return 44;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -179,7 +207,7 @@ class BlockMeta extends bio.Struct {
|
|||
write(bw) {
|
||||
bw.writeHash(this.hash);
|
||||
bw.writeU32(this.height);
|
||||
bw.writeU32(this.time);
|
||||
bw.writeU64(this.time);
|
||||
return bw;
|
||||
}
|
||||
|
||||
|
|
@ -462,5 +490,3 @@ exports.ChainState = ChainState;
|
|||
exports.BlockMeta = BlockMeta;
|
||||
exports.TXRecord = TXRecord;
|
||||
exports.MapRecord = MapRecord;
|
||||
|
||||
module.exports = exports;
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ const Logger = require('blgr');
|
|||
const {safeEqual} = require('bcrypto/lib/safe');
|
||||
const aes = require('bcrypto/lib/aes');
|
||||
const Network = require('../protocol/network');
|
||||
const consensus = require('../protocol/consensus');
|
||||
const Path = require('./path');
|
||||
const common = require('./common');
|
||||
const Wallet = require('./wallet');
|
||||
const Account = require('./account');
|
||||
const Block = require('../primitives/block');
|
||||
const Outpoint = require('../primitives/outpoint');
|
||||
const layouts = require('./layout');
|
||||
const records = require('./records');
|
||||
|
|
@ -342,6 +344,25 @@ class WalletDB extends EventEmitter {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add genesis block.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async saveGenesis() {
|
||||
// Write genesis block.
|
||||
const network = this.network;
|
||||
const block = Block.decode(network.genesisBlock);
|
||||
const entry = {
|
||||
hash: block.hash(),
|
||||
height: 0,
|
||||
time: block.time,
|
||||
prevBlock: consensus.ZERO_HASH
|
||||
};
|
||||
|
||||
await this.addBlock(entry, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the walletdb, wait for the database to close.
|
||||
* @returns {Promise}
|
||||
|
|
@ -453,6 +474,7 @@ class WalletDB extends EventEmitter {
|
|||
await this.syncInitState();
|
||||
await this.syncFilter();
|
||||
await this.syncChain();
|
||||
this.rescanning = false;
|
||||
await this.resend();
|
||||
} finally {
|
||||
this.rescanning = false;
|
||||
|
|
@ -468,8 +490,10 @@ class WalletDB extends EventEmitter {
|
|||
async loadState() {
|
||||
const cache = await this.getState();
|
||||
|
||||
if (!cache)
|
||||
if (!cache) {
|
||||
await this.saveGenesis();
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info('Initialized chain state from the database.');
|
||||
this.hasStateCache = true;
|
||||
|
|
@ -490,14 +514,15 @@ class WalletDB extends EventEmitter {
|
|||
this.logger.info('Initializing database state from server.');
|
||||
|
||||
const b = this.db.batch();
|
||||
const hashes = await this.client.getHashes();
|
||||
const entries = await this.client.getEntries();
|
||||
|
||||
let tip = null;
|
||||
|
||||
for (let height = 0; height < hashes.length; height++) {
|
||||
const hash = hashes[height];
|
||||
const meta = new BlockMeta(hash, height);
|
||||
b.put(layout.h.encode(height), meta.toHash());
|
||||
for (let height = 0; height < entries.length; height++) {
|
||||
const entry = entries[height];
|
||||
assert(entry.height === height);
|
||||
const meta = new BlockMeta(entry.hash, entry.height, entry.time);
|
||||
b.put(layout.h.encode(height), meta.toHashAndTime());
|
||||
tip = meta;
|
||||
}
|
||||
|
||||
|
|
@ -1969,7 +1994,7 @@ class WalletDB extends EventEmitter {
|
|||
}
|
||||
|
||||
// Save tip and state.
|
||||
b.put(layout.h.encode(tip.height), tip.toHash());
|
||||
b.put(layout.h.encode(tip.height), tip.toHashAndTime());
|
||||
b.put(layout.R.encode(), state.encode());
|
||||
|
||||
await b.write();
|
||||
|
|
@ -2257,12 +2282,12 @@ class WalletDB extends EventEmitter {
|
|||
*/
|
||||
|
||||
async getBlock(height) {
|
||||
const hash = await this.db.get(layout.h.encode(height));
|
||||
const data = await this.db.get(layout.h.encode(height));
|
||||
|
||||
if (!hash)
|
||||
if (!data)
|
||||
return null;
|
||||
|
||||
return new BlockMeta(hash, height);
|
||||
return BlockMeta.fromHashAndTime(data, height);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -396,7 +396,7 @@ describe('WalletDB ChainState', function() {
|
|||
}
|
||||
|
||||
assert.strictEqual(wdb.state.startHeight, firstBlock.height);
|
||||
assert.strictEqual(wdb.state.startHash, firstBlock.hash);
|
||||
assert.bufferEqual(wdb.state.startHash, firstBlock.hash);
|
||||
assert.strictEqual(wdb.height, noTXBuffer + blocksPerAction * 2);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer + blocksPerAction * 2);
|
||||
assert.strictEqual(wdb.state.marked, true);
|
||||
|
|
@ -407,7 +407,7 @@ describe('WalletDB ChainState', function() {
|
|||
|
||||
const tip = await wdb.getTip();
|
||||
assert.strictEqual(wdb.state.startHeight, tip.height);
|
||||
assert.strictEqual(wdb.state.startHash, tip.hash);
|
||||
assert.bufferEqual(wdb.state.startHash, tip.hash);
|
||||
assert.strictEqual(wdb.height, noTXBuffer);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer);
|
||||
assert.strictEqual(wdb.state.marked, false);
|
||||
|
|
@ -422,14 +422,14 @@ describe('WalletDB ChainState', function() {
|
|||
firstBlock = blockAndTXs.block;
|
||||
|
||||
assert.strictEqual(wdb.state.startHeight, firstBlock.height);
|
||||
assert.strictEqual(wdb.state.startHash, firstBlock.hash);
|
||||
assert.bufferEqual(wdb.state.startHash, firstBlock.hash);
|
||||
assert.strictEqual(wdb.height, noTXBuffer + i + 1);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer + i + 1);
|
||||
assert.strictEqual(wdb.state.marked, true);
|
||||
}
|
||||
|
||||
assert.strictEqual(wdb.state.startHeight, firstBlock.height);
|
||||
assert.strictEqual(wdb.state.startHash, firstBlock.hash);
|
||||
assert.bufferEqual(wdb.state.startHash, firstBlock.hash);
|
||||
assert.strictEqual(wdb.height, noTXBuffer + blocksPerAction);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer + blocksPerAction);
|
||||
assert.strictEqual(wdb.state.marked, true);
|
||||
|
|
@ -452,7 +452,7 @@ describe('WalletDB ChainState', function() {
|
|||
}
|
||||
|
||||
assert.strictEqual(wdb.state.startHeight, firstBlock.height);
|
||||
assert.strictEqual(wdb.state.startHash, firstBlock.hash);
|
||||
assert.bufferEqual(wdb.state.startHash, firstBlock.hash);
|
||||
assert.strictEqual(wdb.height, noTXBuffer + blocksPerAction * 2);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer + blocksPerAction * 2);
|
||||
assert.strictEqual(wdb.state.marked, true);
|
||||
|
|
@ -463,7 +463,7 @@ describe('WalletDB ChainState', function() {
|
|||
|
||||
const tip = await wdb.getTip();
|
||||
assert.strictEqual(wdb.state.startHeight, tip.height);
|
||||
assert.strictEqual(wdb.state.startHash, tip.hash);
|
||||
assert.bufferEqual(wdb.state.startHash, tip.hash);
|
||||
assert.strictEqual(wdb.height, noTXBuffer);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer);
|
||||
assert.strictEqual(wdb.state.marked, false);
|
||||
|
|
@ -472,14 +472,14 @@ describe('WalletDB ChainState', function() {
|
|||
await progressWithNoTX(wdb);
|
||||
|
||||
assert.strictEqual(wdb.state.startHeight, tip.height);
|
||||
assert.strictEqual(wdb.state.startHash, tip.hash);
|
||||
assert.bufferEqual(wdb.state.startHash, tip.hash);
|
||||
assert.strictEqual(wdb.height, noTXBuffer + i + 1);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer + i + 1);
|
||||
assert.strictEqual(wdb.state.marked, false);
|
||||
}
|
||||
|
||||
assert.strictEqual(wdb.state.startHeight, tip.height);
|
||||
assert.strictEqual(wdb.state.startHash, tip.hash);
|
||||
assert.bufferEqual(wdb.state.startHash, tip.hash);
|
||||
assert.strictEqual(wdb.height, noTXBuffer + blocksPerAction);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer + blocksPerAction);
|
||||
assert.strictEqual(wdb.state.marked, false);
|
||||
|
|
@ -493,14 +493,14 @@ describe('WalletDB ChainState', function() {
|
|||
removeBlocks.push(blockAndTXs);
|
||||
|
||||
assert.strictEqual(wdb.state.startHeight, firstBlock.height);
|
||||
assert.strictEqual(wdb.state.startHash, firstBlock.hash);
|
||||
assert.bufferEqual(wdb.state.startHash, firstBlock.hash);
|
||||
assert.strictEqual(wdb.height, noTXBuffer + blocksPerAction + i + 1);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer + blocksPerAction + i + 1);
|
||||
assert.strictEqual(wdb.state.marked, true);
|
||||
}
|
||||
|
||||
assert.strictEqual(wdb.state.startHeight, firstBlock.height);
|
||||
assert.strictEqual(wdb.state.startHash, firstBlock.hash);
|
||||
assert.bufferEqual(wdb.state.startHash, firstBlock.hash);
|
||||
assert.strictEqual(wdb.height, noTXBuffer + blocksPerAction * 2);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer + blocksPerAction * 2);
|
||||
assert.strictEqual(wdb.state.marked, true);
|
||||
|
|
@ -517,7 +517,7 @@ describe('WalletDB ChainState', function() {
|
|||
await progressWithNoTX(wdb);
|
||||
|
||||
assert.strictEqual(wdb.state.startHeight, 0);
|
||||
assert.strictEqual(wdb.state.startHash, consensus.ZERO_HASH);
|
||||
assert.bufferEqual(wdb.state.startHash, consensus.ZERO_HASH);
|
||||
assert.strictEqual(wdb.height, noTXBuffer);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer);
|
||||
assert.strictEqual(wdb.state.marked, false);
|
||||
|
|
@ -539,7 +539,7 @@ describe('WalletDB ChainState', function() {
|
|||
assert.strictEqual(err.message, 'Corruption');
|
||||
|
||||
assert.strictEqual(wdb.state.startHeight, 0);
|
||||
assert.strictEqual(wdb.state.startHash, consensus.ZERO_HASH);
|
||||
assert.bufferEqual(wdb.state.startHash, consensus.ZERO_HASH);
|
||||
assert.strictEqual(wdb.height, noTXBuffer);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer);
|
||||
assert.strictEqual(wdb.state.marked, false);
|
||||
|
|
@ -551,7 +551,7 @@ describe('WalletDB ChainState', function() {
|
|||
await progressWithNoTX(wdb);
|
||||
|
||||
assert.strictEqual(wdb.state.startHeight, 0);
|
||||
assert.strictEqual(wdb.state.startHash, consensus.ZERO_HASH);
|
||||
assert.bufferEqual(wdb.state.startHash, consensus.ZERO_HASH);
|
||||
assert.strictEqual(wdb.height, noTXBuffer + i + 1);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer + i + 1);
|
||||
assert.strictEqual(wdb.state.marked, false);
|
||||
|
|
@ -559,7 +559,7 @@ describe('WalletDB ChainState', function() {
|
|||
|
||||
const {block} = await progressWithTX(wdb);
|
||||
assert.strictEqual(wdb.state.startHeight, block.height);
|
||||
assert.strictEqual(wdb.state.startHash, block.hash);
|
||||
assert.bufferEqual(wdb.state.startHash, block.hash);
|
||||
assert.strictEqual(wdb.height, noTXBuffer + blocksPerAction + 1);
|
||||
assert.strictEqual(wdb.state.height, noTXBuffer + blocksPerAction + 1);
|
||||
assert.strictEqual(wdb.state.marked, true);
|
||||
|
|
|
|||
|
|
@ -227,12 +227,13 @@ describe('Wallet Records', function() {
|
|||
compareBlockMeta(decoded, data);
|
||||
});
|
||||
|
||||
it('should return block hash', () => {
|
||||
it('should return block hash and time and decode', () => {
|
||||
const data = getRandomBlockMetaData();
|
||||
const meta = new BlockMeta();
|
||||
meta.inject(data);
|
||||
const meta = BlockMeta.fromEntry(data);
|
||||
|
||||
assert.bufferEqual(meta.toHash(), data.hash);
|
||||
const hashAndTime = meta.toHashAndTime();
|
||||
const meta2 = BlockMeta.fromHashAndTime(hashAndTime, data.height);
|
||||
compareBlockMeta(meta, meta2);
|
||||
});
|
||||
|
||||
it('should be created from ChainEntry', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue