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: Add neutrino mode client support #1160

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
7 changes: 5 additions & 2 deletions bin/bcoin
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ for arg in "$@"; do
--daemon)
daemon=1
;;
--spv)
cmd='spvnode'
--neutrino)
cmd='neutrino'
;;
--spv)
cmd='spvnode'
;;
esac
done
Expand Down
42 changes: 42 additions & 0 deletions bin/neutrino
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env node

'use strict';

console.log('Starting bcoin');
process.title = 'bcoin';
const Neutrino = require('../lib/node/neutrino');

const node = new Neutrino({
file: true,
argv: true,
env: true,
logFile: true,
logConsole: true,
logLevel: 'debug',
db: 'leveldb',
memory: false,
workers: true,
loader: require
});

(async () => {
await node.ensure();
await node.open();
await node.connect();
node.startSync();

node.on('full', () => {
console.log('Full node');
});
})().catch((err) => {
console.error(err.stack);
process.exit(1);
});

process.on('unhandledRejection', (err, promise) => {
throw err;
});

process.on('SIGINT', async () => {
await node.close();
});
1 change: 1 addition & 0 deletions lib/bcoin-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ bcoin.node = require('./node');
bcoin.Node = require('./node/node');
bcoin.FullNode = require('./node/fullnode');
bcoin.SPVNode = require('./node/spvnode');
bcoin.Neutrino = require('./node/neutrino');

// Primitives
bcoin.primitives = require('./primitives');
Expand Down
2 changes: 1 addition & 1 deletion lib/bcoin.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ bcoin.define('node', './node');
bcoin.define('Node', './node/node');
bcoin.define('FullNode', './node/fullnode');
bcoin.define('SPVNode', './node/spvnode');

bcoin.define('Neutrino', './node/neutrino');
// Primitives
bcoin.define('primitives', './primitives');
bcoin.define('Address', './primitives/address');
Expand Down
16 changes: 12 additions & 4 deletions lib/blockchain/chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -2006,16 +2006,18 @@ class Chain extends AsyncEmitter {
if (this.options.checkpoints) {
if (this.height < this.network.lastCheckpoint)
return;
}

if (this.tip.time < util.now() - this.network.block.maxTipAge)
} else if (!this.options.neutrino &&
this.tip.time < util.now() - this.network.block.maxTipAge)
return;

if (!this.hasChainwork())
return;

this.synced = true;
this.emit('full');
if (this.options.neutrino)
this.emit('headersFull');
else
this.emit('full');
}

/**
Expand Down Expand Up @@ -2616,6 +2618,7 @@ class ChainOptions {
this.compression = true;

this.spv = false;
this.neutrino = false;
this.bip91 = false;
this.bip148 = false;
this.prune = false;
Expand Down Expand Up @@ -2662,6 +2665,11 @@ class ChainOptions {
this.spv = options.spv;
}

if (options.neutrino != null) {
assert(typeof options.neutrino === 'boolean');
this.neutrino = options.neutrino;
}

if (options.prefix != null) {
assert(typeof options.prefix === 'string');
this.prefix = options.prefix;
Expand Down
51 changes: 51 additions & 0 deletions lib/blockchain/chaindb.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class ChainDB {
this.state = new ChainState();
this.pending = null;
this.current = null;
this.neutrinoState = null;

this.cacheHash = new LRU(this.options.entryCache, null, BufferMap);
this.cacheHeight = new LRU(this.options.entryCache);
Expand Down Expand Up @@ -90,6 +91,11 @@ class ChainDB {
this.logger.info('ChainDB successfully initialized.');
}

if (this.options.neutrino) {
if (!this.neutrinoState)
this.neutrinoState = await this.getNeutrinoState();
}

this.logger.info(
'Chain State: hash=%h tx=%d coin=%d value=%s.',
this.state.tip,
Expand Down Expand Up @@ -1670,6 +1676,29 @@ class ChainDB {
b.put(layout.O.encode(), flags.toRaw());
return b.write();
}

/**
* Get Neutrino State
* @returns {Promise<NeutrinoState>} - Returns neutrino state
*/

async getNeutrinoState() {
const data = await this.db.get(layout.N.encode());
if (!data)
return new NeutrinoState();
return NeutrinoState.fromRaw(data);
}

/**
* Save Neutrino State
* @returns {void}
*/
async saveNeutrinoState() {
const state = this.neutrinoState.toRaw();
const b = this.db.batch();
b.put(layout.N.encode(), state);
return b.write();
}
}

/**
Expand Down Expand Up @@ -1952,6 +1981,28 @@ function fromU32(num) {
return data;
}

class NeutrinoState {
constructor() { // TODO: do we add support for multiple filters?
this.headerHeight = 0;
this.filterHeight = 0;
}

toRaw() {
const bw = bio.write(8);
bw.writeU32(this.headerHeight);
bw.writeU32(this.filterHeight);
return bw.render();
}

static fromRaw(data) {
const state = new NeutrinoState();
const br = bio.read(data);
state.headersHeight = br.readU32();
state.filterHeight = br.readU32();
return state;
}
}

/*
* Expose
*/
Expand Down
4 changes: 4 additions & 0 deletions lib/blockchain/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const bdb = require('bdb');
* O -> chain options
* R -> tip hash
* D -> versionbits deployments
* N -> Neutrino Status
* F[hash] -> filterHeader
* e[hash] -> entry
* h[hash] -> height
* H[height] -> hash
Expand All @@ -33,6 +35,8 @@ const layout = {
O: bdb.key('O'),
R: bdb.key('R'),
D: bdb.key('D'),
N: bdb.key('N'),
F: bdb.key('H', ['hash256']),
e: bdb.key('e', ['hash256']),
h: bdb.key('h', ['hash256']),
H: bdb.key('H', ['uint32']),
Expand Down
43 changes: 43 additions & 0 deletions lib/indexer/filterindexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,49 @@ class FilterIndexer extends Indexer {
this.put(layout.f.encode(hash), gcsFilter.hash());
}

/**
* save filter header
* @param {Hash} blockHash
* @param {Hash} filterHeader
* @param {Hash} filterHash
* @returns {Promise}
*/

async saveFilterHeader(blockHash, filterHeader, filterHash) {
assert(blockHash);
assert(filterHeader);
assert(filterHash);

const filter = new Filter();
filter.header = filterHeader;

await this.blocks.writeFilter(blockHash, filter.toRaw(), this.filterType);
console.log(layout.f.encode(blockHash));
this.put(layout.f.encode(blockHash), filterHash);
}

/**
* Save filter
* @param {Hash} blockHash
* @param {BasicFilter} basicFilter
* @param {Hash} filterHeader
* @returns {Promise}
*/

async saveFilter(blockHash, basicFilter, filterHeader) {
assert(blockHash);
assert(basicFilter);
assert(filterHeader);

const filter = new Filter();
filter.filter = basicFilter.toRaw();
filter.header = filterHeader;

await this.blocks.writeFilter(blockHash, filter.toRaw(), this.filterType);
console.log(layout.f.encode(blockHash));
this.put(layout.f.encode(blockHash), basicFilter.hash());
}

/**
* Prune compact filters.
* @private
Expand Down
78 changes: 78 additions & 0 deletions lib/net/peer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,12 @@ class Peer extends EventEmitter {
case packetTypes.GETHEADERS:
this.request(packetTypes.HEADERS, timeout * 2);
break;
case packetTypes.GETCFHEADERS:
this.request(packetTypes.CFHEADERS, timeout);
break;
case packetTypes.GETCFILTERS:
this.request(packetTypes.CFILTER, timeout);
break;
case packetTypes.GETDATA:
this.request(packetTypes.DATA, timeout * 2);
break;
Expand Down Expand Up @@ -1449,6 +1455,12 @@ class Peer extends EventEmitter {
if (!(this.services & services.NETWORK))
throw new Error('Peer does not support network services.');

if (this.options.neutrino) {
if (!(this.services & services.NODE_COMPACT_FILTERS)) {
throw new Error('Peer does not support Compact Filters.');
}
}

if (this.options.headers) {
if (this.version < common.HEADERS_VERSION)
throw new Error('Peer does not support getheaders.');
Expand Down Expand Up @@ -1745,6 +1757,26 @@ class Peer extends EventEmitter {
this.send(packet);
}

/**
* @param {Number} filterType - `0` = basic
* @param {Number} startHeight - Height to start at.
* @param {Hash} stopHash - Hash to stop at.
* @returns {void}
* @description Send `getcfilters` to peer.
*/
sendGetCFilters(filterType, startHeight, stopHash) {
const packet = new packets.GetCFiltersPacket(
filterType,
startHeight,
stopHash);

this.logger.debug(
'Sending getcfilters (type=%d, startHeight=%d, stopHash=%h).',
filterType, startHeight, stopHash);

this.send(packet);
}

/**
* Send `cfheaders` to peer.
* @param {Number} filterType
Expand All @@ -1767,6 +1799,27 @@ class Peer extends EventEmitter {
this.send(packet);
}

/**
* @param {Number} filterType
* @param {Number} startHeight
* @param {Hash} stopHash
* @returns {void}
* @description Send `getcfheaders` to peer.
*/

sendGetCFHeaders(filterType, startHeight, stopHash) {
const packet = new packets.GetCFHeadersPacket(
filterType,
startHeight,
stopHash);

this.logger.debug(
'Sending getcfheaders (type=%d, start=%h, stop=%h).',
filterType, startHeight, stopHash);

this.send(packet);
}

/**
* send `cfcheckpt` to peer.
* @param {Number} filterType
Expand All @@ -1787,6 +1840,25 @@ class Peer extends EventEmitter {
this.send(packet);
}

/**
* Send `getcfcheckpt` to peer.
* @param {Number} filterType
* @param {Hash} stopHash
* @returns {void}
*/

sendGetCFCheckpt(filterType, stopHash) {
const packet = new packets.GetCFCheckptPacket(
filterType,
stopHash);

this.logger.debug(
'Sending getcfcheckpt (type=%d, stop=%h).',
filterType, stopHash);

this.send(packet);
}

/**
* Send `mempool` to peer.
*/
Expand Down Expand Up @@ -2080,6 +2152,7 @@ class PeerOptions {
this.agent = common.USER_AGENT;
this.noRelay = false;
this.spv = false;
this.neutrino = false;
this.compact = false;
this.headers = false;
this.banScore = common.BAN_SCORE;
Expand Down Expand Up @@ -2143,6 +2216,11 @@ class PeerOptions {
this.spv = options.spv;
}

if (options.neutrino != null) {
assert(typeof options.neutrino === 'boolean');
this.neutrino = options.neutrino;
}

if (options.compact != null) {
assert(typeof options.compact === 'boolean');
this.compact = options.compact;
Expand Down
Loading