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
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ process and allows parallel rescans.
- `compactInterval` - what is the current compaction interval config.
- `nextCompaction` - when will the next compaction trigger after restart.
- `lastCompaction` - when was the last compaction run.
- Introduce `scan interactive` hook (start, filter)
- Introduce `scan interactive` hook (start, filter, fullLock)

### Node HTTP Client:
- Introduce `scanInteractive` method that starts interactive rescan.
Expand All @@ -43,6 +43,9 @@ process and allows parallel rescans.
- Add `getFee`, an HTTP alternative to estimateFee socket call.

### Wallet Changes
- Add migration that recalculates txdb balances to fix any inconsistencies.
- Wallet will now use `interactive scan` for initial sync(on open) and rescan.

#### Configuration
- Wallet now has option `wallet-migrate-no-rescan`/`migrate-no-rescan` if you
want to disable rescan when migration recommends it. It may result in the
Expand All @@ -54,21 +57,22 @@ process and allows parallel rescans.

#### Wallet API

- Add migration that recalculates txdb balances to fix any inconsistencies.
- WalletNode now emits `open` and `close` events.
- WalletDB Now emits events for: `open`, `close`, `connect`, `disconnect`.
- WalletDB
- `open()` no longer calls `connect` and needs separate call `connect`.
- `open()` no longer calls scan, instead only rollbacks and waits for
sync to do the rescan.
- emits events for: `open`, `close`, `connect`, `disconnect`, `sync done`.

### Wallet HTTP Client
#### Wallet HTTP
- All transaction creating endpoints now accept `hardFee` for specifying the
exact fee.
- All transaction sending endpoints now fundlock/queue tx creation. (no more
conflicting transactions)
- Add options to `getNames` for passing `own`.


## v6.0.0

### Node and Wallet HTTP API
Expand Down
51 changes: 47 additions & 4 deletions lib/blockchain/chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -2266,16 +2266,55 @@ class Chain extends AsyncEmitter {
}
}

/** @typedef {import('./common').ScanAction} ScanAction */

/**
* @callback ScanInteractiveIterCB
* @param {ChainEntry} entry
* @param {TX[]} txs
* @returns {Promise<ScanAction>}
*/

/**
* Interactive scan the blockchain for transactions containing specified
* address hashes. Allows repeat and abort.
* @param {Hash|Number} start - Block hash or height to start at.
* @param {BloomFilter} filter - Starting bloom filter containing tx,
* address and name hashes.
* @param {Function} iter - Iterator.
* @param {ScanInteractiveIterCB} iter - Iterator.
* @param {Boolean} [fullLock=false]
* @returns {Promise}
*/

async scanInteractive(start, filter, iter) {
async scanInteractive(start, filter, iter, fullLock = false) {
if (fullLock) {
const unlock = await this.locker.lock();
try {
// We lock the whole chain, no longer lock per block scan.
return await this._scanInteractive(start, filter, iter, false);
} catch (e) {
this.logger.debug('Scan(interactive) errored. Error: %s', e.message);
throw e;
} finally {
unlock();
}
}

return this._scanInteractive(start, filter, iter, true);
}

/**
* Interactive scan the blockchain for transactions containing specified
* address hashes. Allows repeat and abort.
* @param {Hash|Number} start - Block hash or height to start at.
* @param {BloomFilter} filter - Starting bloom filter containing tx,
* address and name hashes.
* @param {ScanInteractiveIterCB} iter - Iterator.
* @param {Boolean} [lockPerScan=true] - if we should lock per block scan.
* @returns {Promise}
*/

async _scanInteractive(start, filter, iter, lockPerScan = true) {
if (start == null)
start = this.network.genesis.hash;

Expand All @@ -2287,7 +2326,10 @@ class Chain extends AsyncEmitter {
let hash = start;

while (hash != null) {
const unlock = await this.locker.lock();
let unlock;

if (lockPerScan)
unlock = await this.locker.lock();

try {
const {entry, txs} = await this.db.scanBlock(hash, filter);
Expand Down Expand Up @@ -2333,7 +2375,8 @@ class Chain extends AsyncEmitter {
this.logger.debug('Scan(interactive) errored. Error: %s', e.message);
throw e;
} finally {
unlock();
if (lockPerScan)
unlock();
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion lib/blockchain/chaindb.js
Original file line number Diff line number Diff line change
Expand Up @@ -1612,12 +1612,18 @@ class ChainDB {
this.logger.info('Finished scanning %d blocks.', total);
}

/**
* @typedef {Object} ScanBlockResult
* @property {ChainEntry} entry
* @property {TX[]} txs
*/

/**
* Interactive scans block checks.
* @param {Hash|Number} blockID - Block hash or height to start at.
* @param {BloomFilter} [filter] - Starting bloom filter containing tx,
* address and name hashes.
* @returns {Promise}
* @returns {Promise<ScanBlockResult>}
*/

async scanBlock(blockID, filter) {
Expand Down
36 changes: 36 additions & 0 deletions lib/blockchain/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,39 @@ exports.scanActions = {
REPEAT_ADD: 4,
REPEAT: 5
};

/**
* @typedef {Object} ActionAbort
* @property {exports.scanActions} type - ABORT
*/

/**
* @typedef {Object} ActionNext
* @property {exports.scanActions} type - NEXT
*/

/**
* @typedef {Object} ActionRepeat
* @property {exports.ScanAction} type - REPEAT
*/

/**
* @typedef {Object} ActionRepeatAdd
* @property {exports.scanActions} type - REPEAT_ADD
* @property {Buffer[]} chunks
*/

/**
* @typedef {Object} ActionRepeatSet
* @property {exports.scanActions} type - REPEAT_SET
* @property {BloomFilter} filter
*/

/**
* @typedef {ActionAbort
* | ActionNext
* | ActionRepeat
* | ActionRepeatAdd
* | ActionRepeatSet
* } ScanAction
*/
5 changes: 3 additions & 2 deletions lib/client/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,16 +370,17 @@ class NodeClient extends Client {
* Rescan for any missed transactions. (Interactive)
* @param {Number|Hash} start - Start block.
* @param {BloomFilter} [filter]
* @param {Boolean} [fullLock=false]
* @returns {Promise}
*/

rescanInteractive(start, filter = null) {
rescanInteractive(start, filter = null, fullLock = false) {
if (start == null)
start = 0;

assert(typeof start === 'number' || Buffer.isBuffer(start));

return this.call('rescan interactive', start, filter);
return this.call('rescan interactive', start, filter, fullLock);
}
}

Expand Down
2 changes: 2 additions & 0 deletions lib/client/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -1600,4 +1600,6 @@ class Wallet extends EventEmitter {
* Expose
*/

WalletClient.Wallet = Wallet;

module.exports = WalletClient;
6 changes: 4 additions & 2 deletions lib/node/fullnode.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,11 +369,13 @@ class FullNode extends Node {
* @param {Number|Hash} start - Start block.
* @param {BloomFilter} filter
* @param {Function} iter - Iterator.
* @param {Boolean} [fullLock=false] - lock the whole chain instead of per
* scan.
* @returns {Promise}
*/

scanInteractive(start, filter, iter) {
return this.chain.scanInteractive(start, filter, iter);
scanInteractive(start, filter, iter, fullLock = false) {
return this.chain.scanInteractive(start, filter, iter, fullLock);
}

/**
Expand Down
13 changes: 7 additions & 6 deletions lib/node/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ class HTTP extends Server {
const valid = new Validator(args);
const start = valid.uintbhash(0);
const rawFilter = valid.buf(1);
const fullLock = valid.bool(2, false);
let filter = socket.filter;

if (start == null)
Expand All @@ -720,7 +721,7 @@ class HTTP extends Server {
if (rawFilter)
filter = BloomFilter.fromRaw(rawFilter);

return this.scanInteractive(socket, start, filter);
return this.scanInteractive(socket, start, filter, fullLock);
});
}

Expand Down Expand Up @@ -859,10 +860,11 @@ class HTTP extends Server {
* @param {WebSocket} socket
* @param {Hash} start
* @param {BloomFilter} filter
* @param {Boolean} [fullLock=false]
* @returns {Promise}
*/

async scanInteractive(socket, start, filter) {
async scanInteractive(socket, start, filter, fullLock = false) {
const iter = async (entry, txs) => {
const block = entry.encode();
const raw = [];
Expand Down Expand Up @@ -921,12 +923,11 @@ class HTTP extends Server {
};

try {
await this.node.scanInteractive(start, filter, iter);
await this.node.scanInteractive(start, filter, iter, fullLock);
} catch (err) {
return socket.call('block rescan interactive abort', err.message);
await socket.call('block rescan interactive abort', err.message);
throw err;
}

return null;
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/wallet/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ class Account extends bio.Struct {
* Allocate new lookahead addresses if necessary.
* @param {Number} receiveDepth
* @param {Number} changeDepth
* @returns {Promise} - Returns {@link WalletKey}.
* @returns {Promise<WalletKey?>}
*/

async syncDepth(b, receive, change) {
Expand Down
37 changes: 33 additions & 4 deletions lib/wallet/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ 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),
'block disconnect': entry => [parseEntry(entry)],
'block rescan': (entry, txs) => parseBlock(entry, txs),
'block rescan interactive': (entry, txs) => parseBlock(entry, txs),
'chain reset': entry => [parseEntry(entry)],
'tx': tx => [TX.decode(tx)]
};
Expand Down Expand Up @@ -74,10 +76,27 @@ class WalletClient extends NodeClient {
return super.setFilter(filter.encode());
}

/**
* Rescan for any missed transactions.
* @param {Number|Hash} start - Start block.
* @returns {Promise}
*/

async rescan(start) {
return super.rescan(start);
}

/**
* Rescan interactive for any missed transactions.
* @param {Number|Hash} start - Start block.
* @param {Boolean} [fullLock=false]
* @returns {Promise}
*/

async rescanInteractive(start, fullLock) {
return super.rescanInteractive(start, null, fullLock);
}

async getNameStatus(nameHash) {
const json = await super.getNameStatus(nameHash);
return NameState.fromJSON(json);
Expand All @@ -94,6 +113,9 @@ class WalletClient extends NodeClient {
*/

function parseEntry(data) {
if (!data)
return null;

// 32 hash
// 4 height
// 4 nonce
Expand All @@ -112,17 +134,24 @@ 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
};
}

function parseBlock(entry, txs) {
const block = parseEntry(entry);
assert(block);
const out = [];

for (const tx of txs)
Expand Down
2 changes: 2 additions & 0 deletions lib/wallet/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class WalletNode extends Node {
await this.handleOpen();

this.logger.info('Wallet node is loaded.');
this.emit('open');
}

/**
Expand All @@ -134,6 +135,7 @@ class WalletNode extends Node {
await this.wdb.disconnect();
await this.wdb.close();
await this.handleClose();
this.emit('close');
}
}

Expand Down
Loading
Loading