Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wallet Sync Updates #883

Merged
merged 13 commits into from
Aug 20, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
wdb: Add sanity checks to the wdb.addBlock.
wdb/client: fix standalone ChainEntry deserialization and add prevBlock.
nodech committed Aug 20, 2024
commit a405ea9009a574fb7d8e0e84cd60d0876f1d749c
15 changes: 11 additions & 4 deletions lib/wallet/client.js
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ const NodeClient = require('../client/node');
const TX = require('../primitives/tx');
const Coin = require('../primitives/coin');
const NameState = require('../covenants/namestate');
const {encoding} = require('bufio');

const parsers = {
'block connect': (entry, txs) => parseBlock(entry, txs),
@@ -115,12 +116,18 @@ function parseEntry(data) {

assert(Buffer.isBuffer(data));
// Just enough to read the three data below
assert(data.length >= 44);
assert(data.length >= 80);

const hash = data.slice(0, 32);
const height = encoding.readU32(data, 32);
const time = encoding.readU64(data, 40);
const prevBlock = data.slice(48, 80);

return {
hash: data.slice(0, 32),
height: data.readUInt32LE(32),
time: data.readUInt32LE(40)
hash,
height,
time,
prevBlock
};
}

48 changes: 43 additions & 5 deletions lib/wallet/walletdb.js
Original file line number Diff line number Diff line change
@@ -517,7 +517,7 @@ class WalletDB extends EventEmitter {
* Rescan blockchain from a given height.
* Needs this.rescanning = true to be set from the caller.
* @private
* @param {Number?} height
* @param {Number} [height=this.state.startHeight]
* @returns {Promise}
*/

@@ -2181,7 +2181,7 @@ class WalletDB extends EventEmitter {

/**
* Get a wallet block meta.
* @param {Hash} hash
* @param {Number} height
* @returns {Promise<BlockMeta?>}
*/

@@ -2305,7 +2305,7 @@ class WalletDB extends EventEmitter {
* @private
* @param {ChainEntry} entry
* @param {TX[]} txs
* @returns {Promise}
* @returns {Promise<Number>} - Number of transactions added.
*/

async _addBlock(entry, txs) {
@@ -2315,7 +2315,17 @@ class WalletDB extends EventEmitter {
this.logger.warning(
'WalletDB is connecting low blocks (%d).',
tip.height);
return 0;

const block = await this.getBlock(tip.height);
assert(block);

if (!entry.hash.equals(block.hash)) {
// Maybe we run syncChain here.
this.logger.warning(
'Unusual reorg at low height (%d).',
tip.height);
}
return -1;
}

if (tip.height >= this.network.block.slowHeight)
@@ -2329,9 +2339,37 @@ class WalletDB extends EventEmitter {
// updated before the block was fully
// processed (in the case of a crash).
this.logger.warning('Already saw WalletDB block (%d).', tip.height);

const block = await this.getBlock(tip.height);
assert(block);

if (!entry.hash.equals(block.hash)) {
this.logger.warning(
'Unusual reorg at the same height (%d).',
tip.height);

// Maybe we can run syncChain here.
return -1;
}
} else if (tip.height !== this.state.height + 1) {
await this._rescan(this.state.height);
return 0;
return -1;
}

let block;

if (tip.height > 2) {
block = await this.getBlock(tip.height - 1);
assert(block);
}

if (block && !block.hash.equals(entry.prevBlock)) {
// We can trigger syncChain here as well.
this.logger.warning(
'Unusual reorg at height (%d).',
tip.height);

return -1;
}

const walletTxs = [];
36 changes: 21 additions & 15 deletions test/util/wallet.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
'use strict';

const assert = require('bsert');
const blake2b = require('bcrypto/lib/blake2b');
const random = require('bcrypto/lib/random');
const Block = require('../../lib/primitives/block');
const ChainEntry = require('../../lib/blockchain/chainentry');
const Input = require('../../lib/primitives/input');
const Outpoint = require('../../lib/primitives/outpoint');
const {ZERO_HASH} = require('../../lib/protocol/consensus');

const walletUtils = exports;

walletUtils.fakeBlock = (height) => {
const prev = blake2b.digest(fromU32((height - 1) >>> 0));
const hash = blake2b.digest(fromU32(height >>> 0));
const root = blake2b.digest(fromU32((height | 0x80000000) >>> 0));
walletUtils.fakeBlock = (height, prevSeed = 0, seed = prevSeed) => {
assert(height >= 0);
const prev = height === 0 ? ZERO_HASH : blake2b.digest(fromU32(((height - 1) ^ prevSeed) >>> 0));
const hash = blake2b.digest(fromU32((height ^ seed) >>> 0));
const root = blake2b.digest(fromU32((height | 0x80000000 ^ seed) >>> 0));

return {
hash: hash,
@@ -36,22 +38,26 @@ walletUtils.dummyInput = () => {
return Input.fromOutpoint(new Outpoint(hash, 0));
};

walletUtils.nextBlock = (wdb) => {
return walletUtils.fakeBlock(wdb.state.height + 1);
walletUtils.nextBlock = (wdb, prevSeed = 0, seed = prevSeed) => {
return walletUtils.fakeBlock(wdb.state.height + 1, prevSeed, seed);
};

walletUtils.curBlock = (wdb) => {
return walletUtils.fakeBlock(wdb.state.height);
walletUtils.curBlock = (wdb, prevSeed = 0, seed = prevSeed) => {
return walletUtils.fakeBlock(wdb.state.height, prevSeed, seed);
};

walletUtils.nextEntry = (wdb) => {
const cur = walletUtils.curEntry(wdb);
const next = new Block(walletUtils.nextBlock(wdb));
return ChainEntry.fromBlock(next, cur);
walletUtils.fakeEntry = (height, prevSeed = 0, curSeed = prevSeed) => {
const cur = walletUtils.fakeBlock(height, prevSeed, curSeed);
return new ChainEntry(cur);;
};

walletUtils.curEntry = (wdb) => {
return new ChainEntry(walletUtils.curBlock(wdb));
walletUtils.nextEntry = (wdb, curSeed = 0, nextSeed = curSeed) => {
const next = walletUtils.nextBlock(wdb, curSeed, nextSeed);
return new ChainEntry(next);
};

walletUtils.curEntry = (wdb, prevSeed = 0, seed = prevSeed) => {
return walletUtils.fakeEntry(wdb.state.height, seed);
};

function fromU32(num) {
34 changes: 25 additions & 9 deletions test/wallet-coinselection-test.js
Original file line number Diff line number Diff line change
@@ -7,11 +7,30 @@ const {
WalletDB,
policy
} = require('..');
const {BlockMeta} = require('../lib/wallet/records');

// Use main instead of regtest because (deprecated)
// CoinSelector.MAX_FEE was network agnostic
const network = Network.get('main');

function dummyBlock(tipHeight) {
const height = tipHeight + 1;
const hash = Buffer.alloc(32);
hash.writeUInt16BE(height);

const prevHash = Buffer.alloc(32);
prevHash.writeUInt16BE(tipHeight);

const dummyBlock = {
hash,
height,
time: Date.now(),
prevBlock: prevHash
};

return dummyBlock;
}

async function fundWallet(wallet, amounts) {
assert(Array.isArray(amounts));

@@ -21,15 +40,8 @@ async function fundWallet(wallet, amounts) {
mtx.addOutput(addr, amt);
}

const height = wallet.wdb.height + 1;
const hash = Buffer.alloc(32);
hash.writeUInt16BE(height);
const dummyBlock = {
hash,
height,
time: Date.now()
};
await wallet.wdb.addBlock(dummyBlock, [mtx.toTX()]);
const dummy = dummyBlock(wallet.wdb.height);
await wallet.wdb.addBlock(dummy, [mtx.toTX()]);
}

describe('Wallet Coin Selection', function () {
@@ -41,6 +53,10 @@ describe('Wallet Coin Selection', function () {
await wdb.open();
wdb.height = network.txStart + 1;
wdb.state.height = wdb.height;

const dummy = dummyBlock(network.txStart + 1);
const record = BlockMeta.fromEntry(dummy);
await wdb.setTip(record);
wallet = wdb.primary;
});

Loading