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

feat: added wallet side integration for neutrino #1159

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
42 changes: 41 additions & 1 deletion lib/blockchain/chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class Chain extends AsyncEmitter {

this.orphanMap = new BufferMap();
this.orphanPrev = new BufferMap();

this.getPrunedMap = new BufferMap();
}

/**
Expand Down Expand Up @@ -1368,7 +1370,18 @@ class Chain extends AsyncEmitter {
}

// Do we already have this block?
if (await this.hasEntry(hash)) {
const existingEntry = await this.getEntry(hash);

// FOR EDUCATIONAL PURPOSES ONLY: save block without checking anything
if (existingEntry && this.getPrunedMap.has(hash)) {
block = block.toBlock();
await this.db.updateNeutrinoSave();
await this.db.save(existingEntry, block, new CoinView());
await this.db.updateNeutrinoSave();
return existingEntry;
}

if (existingEntry) {
this.logger.debug('Already have block: %h.', block.hash());
throw new VerifyError(block, 'duplicate', 'duplicate', 0);
}
Expand Down Expand Up @@ -1925,6 +1938,33 @@ class Chain extends AsyncEmitter {
return this.db.getBlock(hash);
}

async getBlockPeer(hash, filter) {
let block = await this.db.getBlock(hash);
if (block) {
let entry = await this.getEntry(hash);
assert(entry.hash.equals(hash));
return block;
} else {
this.logger.warning('Block not found, attempting to download');

// Ensure hash not height
hash = await this.db.getHash(hash);

// FOR EDUCATIONAL PURPOSES ONLY: flag block for re-downloading
const wait = new Promise((resolve, reject) => {
this.getPrunedMap.set(hash, resolve);
});

await this.emitAsync('getprunedblock', hash);
await wait;
block = await this.db.getBlock(hash);
let entry = await this.getEntry(hash);
assert(entry.hash.equals(hash));

return block;
}
}

/**
* Retrieve a block from the database (not filled with coins).
* @param {Hash} block
Expand Down
14 changes: 12 additions & 2 deletions lib/blockchain/chaindb.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class ChainDB {
this.pending = null;
this.current = null;

this.neutrinoSave = false;

this.cacheHash = new LRU(this.options.entryCache, null, BufferMap);
this.cacheHeight = new LRU(this.options.entryCache);

Expand Down Expand Up @@ -1001,7 +1003,7 @@ class ChainDB {
*/

async getRawBlock(block) {
if (this.options.spv)
if (this.options.spv && !this.options.neutrino)
return null;

const hash = await this.getHash(block);
Expand Down Expand Up @@ -1150,6 +1152,14 @@ class ChainDB {
* @returns {Promise}
*/

async updateNeutrinoSave () {
if(this.neutrinoSave) {
this.neutrinoSave = false;
} else {
this.neutrinoSave = true;
}
}

async save(entry, block, view) {
this.start();
try {
Expand Down Expand Up @@ -1478,7 +1488,7 @@ class ChainDB {
async saveBlock(entry, block, view) {
const hash = block.hash();

if (this.options.spv)
if (this.options.spv && !this.neutrinoSave)
return;

// Write actual block data.
Expand Down
4 changes: 4 additions & 0 deletions lib/client/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ class NodeClient extends Client {
return this.get(`/filter/${filter}`);
}

getBlockPeer(hash, filter) {
return this.call('get block peer', hash, filter);
}

/**
* Add a transaction to the mempool and broadcast it.
* @param {TX} tx
Expand Down
8 changes: 8 additions & 0 deletions lib/net/pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -2316,6 +2316,14 @@ class Pool extends EventEmitter {
}

// Block was orphaned.

const resolve = this.chain.getPrunedMap.get(hash);
if (resolve) {
this.logger.warning('Received pruned block by special request');
this.chain.getPrunedMap.delete(hash);
resolve();
}

if (!entry) {
if (this.checkpoints) {
this.logger.warning(
Expand Down
7 changes: 7 additions & 0 deletions lib/node/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,13 @@ class HTTP extends Server {
return null;
});

socket.hook('get block peer', (...args) => {
const valid = new Validator(args);
const hash = valid.hash(0);
const filter = valid.buf(1);
return this.pool.getBlockPeer(hash, filter);
})

socket.hook('estimate fee', (...args) => {
const valid = new Validator(args);
const blocks = valid.u32(0);
Expand Down
13 changes: 13 additions & 0 deletions lib/wallet/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const NodeClient = require('../client/node');
const util = require('../utils/util');
const TX = require('../primitives/tx');
const hash256 = require('bcrypto/lib/hash256');
const WalletKey = require('./walletkey');
const Filter = require('../primitives/filter');

const parsers = {
'block connect': (entry, txs) => parseBlock(entry, txs),
Expand Down Expand Up @@ -71,6 +73,17 @@ class WalletClient extends NodeClient {
return super.setFilter(filter.toRaw());
}

/**
* Check filter against wallet key ring
* @param {WalletKey} ring
* @param {Filter} filter
* @returns {Promise}
*/

async getBlockFromNode(hash, filter) {
return super.getBlockPeer(hash, filter);
}

async rescan(start) {
if (Buffer.isBuffer(start))
start = util.revHex(start);
Expand Down
21 changes: 20 additions & 1 deletion lib/wallet/nodeclient.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

const assert = require('bsert');
const AsyncEmitter = require('bevent');
const WalletKey = require('./walletkey');
const Filter = require('../primitives/filter');

/**
* Node Client
Expand Down Expand Up @@ -37,7 +39,7 @@ class NodeClient extends AsyncEmitter {

init() {
this.node.chain.on('connect', async (entry, block) => {
if (!this.opened)
if (!this.opened || this.node.neutrino)
return;

await this.emitAsync('block connect', entry, block.txs);
Expand All @@ -50,6 +52,12 @@ class NodeClient extends AsyncEmitter {
await this.emitAsync('block disconnect', entry);
});

this.node.pool.on('cfilter', async (blockHeight, filter) => {
if(!this.opened) return;

await this.emitAsync('cfilter', blockHeight, filter);
})

this.node.on('tx', (tx) => {
if (!this.opened)
return;
Expand Down Expand Up @@ -134,6 +142,10 @@ class NodeClient extends AsyncEmitter {
return entry;
}

async getBlockFromNode(hash, filter) {
await this.node.chain.getBlockPeer(hash, filter);
}

/**
* Send a transaction. Do not wait for promise.
* @param {TX} tx
Expand Down Expand Up @@ -174,6 +186,13 @@ class NodeClient extends AsyncEmitter {
this.node.pool.queueFilterLoad();
}

/**
* Check filter against wallet key ring
* @param {WalletKey} ring
* @param {Filter} filter
* @returns {Promise}
*/

/**
* Estimate smart fee.
* @param {Number?} blocks
Expand Down
13 changes: 13 additions & 0 deletions lib/wallet/nullclient.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

const assert = require('bsert');
const EventEmitter = require('events');
const WalletKey = require('./walletkey');
const Filter = require('../primitives/filter');

/**
* Null Client
Expand Down Expand Up @@ -130,6 +132,17 @@ class NullClient extends EventEmitter {
this.wdb.emit('reset filter');
}

/**
* Check filter against wallet key ring
* @param {WalletKey} ring
* @param {Filter} filter
* @returns {Promise}
*/

async getBlockFromNode(hash, filter) {
;
}

/**
* Esimate smart fee.
* @param {Number?} blocks
Expand Down
2 changes: 2 additions & 0 deletions lib/wallet/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class Wallet extends EventEmitter {
this.writeLock = new Lock();
this.fundLock = new Lock();

this.neutrino = false;

this.wid = 0;
this.id = null;
this.watchOnly = false;
Expand Down
39 changes: 39 additions & 0 deletions lib/wallet/walletdb.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ class WalletDB extends EventEmitter {
this.emit('error', e);
}
});

this.client.bind('cfilter', async (blockHeight, filter) => {
try {
await this.checkFilter(blockHeight, filter);
} catch (e) {
this.emit('error', e);
}
})
}

/**
Expand Down Expand Up @@ -568,6 +576,37 @@ class WalletDB extends EventEmitter {
return this.client.resetFilter();
}

async checkFilter (blockHash, filter) {
const gcsKey = blockHash.slice(0, 16);

const piter = this.db.iterator({
gte: layout.p.min(),
lte: layout.p.max()
});

await piter.each(async (key) => {
const [data] = layout.p.decode(key);
// todo: check filter
let match = filter.match(gcsKey, data);
if (match)
await this.client.getBlockFromNode(blockHash, filter);
});

const oiter = this.db.iterator({
gte: layout.o.min(),
lte: layout.o.max()
});

await oiter.each(async (key) => {
const [hash, index] = layout.o.decode(key);
const outpoint = new Outpoint(hash, index);
const data = outpoint.toRaw();
let match = filter.match(gcsKey, data);
if (match)
await this.client.getBlockFromNode(blockHash, filter);
});
}

/**
* Backup the wallet db.
* @param {String} path
Expand Down