forked from handshake-org/hsd
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port of bcoin-org/bcoin#755 Co-authored-by: Matthew Zipkin <pinheadmz@gmail.com>
- Loading branch information
Showing
3 changed files
with
382 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,289 @@ | ||
'use strict'; | ||
|
||
const assert = require('bsert'); | ||
const FullNode = require('../lib/node/fullnode'); | ||
const SPVNode = require('../lib/node/spvnode'); | ||
const {forEvent} = require('./util/common'); | ||
|
||
const ports = { | ||
full: { | ||
p2p: 14041, | ||
node: 14042, | ||
wallet: 14043, | ||
nsPort: 25449, | ||
rsPort: 25450 | ||
}, | ||
spv: { | ||
p2p: 14141, | ||
node: 14142, | ||
wallet: 14143, | ||
nsPort: 25549, | ||
rsPort: 25550 | ||
} | ||
}; | ||
|
||
const node = new FullNode({ | ||
network: 'regtest', | ||
workers: true, | ||
listen: true, | ||
bip37: true, | ||
port: ports.full.p2p, | ||
httpPort: ports.full.node, | ||
nsPort: ports.full.nsPort, | ||
rsPort: ports.full.rsPort, | ||
maxOutbound: 1, | ||
seeds: [], | ||
memory: true, | ||
plugins: [require('../lib/wallet/plugin')], | ||
env: { | ||
'HSD_WALLET_HTTP_PORT': (ports.full.wallet).toString() | ||
} | ||
}); | ||
|
||
const spvnode = new SPVNode({ | ||
network: 'regtest', | ||
workers: true, | ||
listen: true, | ||
port: ports.spv.p2p, | ||
httpPort: ports.spv.node, | ||
nsPort: ports.spv.nsPort, | ||
rsPort: ports.spv.rsPort, | ||
maxOutbound: 1, | ||
seeds: [], | ||
nodes: [`127.0.0.1:${ports.full.p2p}`], | ||
memory: true, | ||
plugins: [require('../lib/wallet/plugin')], | ||
env: { | ||
'HSD_WALLET_HTTP_PORT': (ports.spv.wallet).toString() | ||
} | ||
}); | ||
|
||
const chain = node.chain; | ||
const miner = node.miner; | ||
const {wdb: fullwdb} = node.require('walletdb'); | ||
const {wdb: spvwdb} = spvnode.require('walletdb'); | ||
|
||
// Test reorg size must be lower than this. | ||
// This is used to set temporary coinbase maturity, | ||
// so reorged coinbases don't end up in the coin selector | ||
// for Full node wallet. | ||
const REORG_MAX = 15; | ||
|
||
let wallet = null; | ||
let spvwallet = null; | ||
let spvaddr = null; | ||
let tip1 = null; | ||
let tip2 = null; | ||
|
||
async function mineBlock(tip) { | ||
const job = await miner.createJob(tip); | ||
return await job.mineAsync(); | ||
} | ||
|
||
describe('SPV Node Sync', function() { | ||
this.timeout(10000); | ||
// back up | ||
const coinbaseMaturity = node.network.coinbaseMaturity; | ||
|
||
if (process.browser) | ||
this.skip(); | ||
|
||
before(async () => { | ||
await node.open(); | ||
await spvnode.open(); | ||
await node.connect(); | ||
await spvnode.connect(); | ||
spvnode.startSync(); | ||
node.network.coinbaseMaturity = REORG_MAX + 1; | ||
}); | ||
|
||
after(async () => { | ||
await node.close(); | ||
await spvnode.close(); | ||
node.network.coinbaseMaturity = coinbaseMaturity; | ||
}); | ||
|
||
it('should check SPV is synced to fullnode', async () => { | ||
assert.deepStrictEqual(node.chain.tip, spvnode.chain.tip); | ||
}); | ||
|
||
it('should open miner and wallets', async () => { | ||
wallet = await fullwdb.create(); | ||
miner.addresses.length = 0; | ||
miner.addAddress(await wallet.receiveAddress()); | ||
|
||
spvwallet = await spvwdb.create(); | ||
spvaddr = await spvwallet.receiveAddress(); | ||
}); | ||
|
||
it('should mine 90 blocks', async () => { | ||
for (let i = 0; i < 90; i++) { | ||
const block = await miner.mineBlock(); | ||
assert(block); | ||
|
||
const spvBlockEvent = forEvent(spvnode, 'block'); | ||
|
||
await chain.add(block); | ||
|
||
// Check SPV & Full nodes are in sync | ||
await spvBlockEvent; | ||
|
||
assert.deepStrictEqual(node.chain.tip, spvnode.chain.tip); | ||
} | ||
// Full node wallet needs to catch up to miner | ||
await fullwdb.rescan(0); | ||
}); | ||
|
||
it('should mine competing chains of 10 blocks', async function () { | ||
for (let i = 0; i < 10; i++) { | ||
const block1 = await mineBlock(tip1); | ||
const block2 = await mineBlock(tip2); | ||
|
||
const spvBlockEvent = forEvent(spvnode, 'block'); | ||
await chain.add(block1); | ||
await chain.add(block2); | ||
|
||
assert.bufferEqual(chain.tip.hash, block1.hash()); | ||
|
||
tip1 = await chain.getEntry(block1.hash()); | ||
tip2 = await chain.getEntry(block2.hash()); | ||
|
||
assert(tip1); | ||
assert(tip2); | ||
|
||
assert(!await chain.isMainChain(tip2)); | ||
|
||
// Check SPV & Full nodes are in sync after every block | ||
await spvBlockEvent; | ||
|
||
assert.deepStrictEqual(node.chain.tip, spvnode.chain.tip); | ||
} | ||
}); | ||
|
||
it('should send a tx from chain 1 to SPV node', async () => { | ||
const balanceEvent = forEvent(spvwallet, 'balance'); | ||
await wallet.send({ | ||
outputs: [{ | ||
value: 1012345678, | ||
address: spvaddr | ||
}] | ||
}); | ||
|
||
await balanceEvent; | ||
const balance = await spvwallet.getBalance(); | ||
assert.strictEqual(balance.unconfirmed, 1012345678); | ||
}); | ||
|
||
it('should mine a block and confirm a tx', async () => { | ||
const blockEvent = forEvent(spvnode, 'block'); | ||
const balanceEvent = forEvent(spvwallet, 'balance'); | ||
|
||
const block = await miner.mineBlock(); | ||
assert(block); | ||
await chain.add(block); | ||
|
||
// Check SPV & Full nodes are in sync | ||
await blockEvent; | ||
assert.deepStrictEqual(node.chain.tip, spvnode.chain.tip); | ||
|
||
// Check SPV wallet balance | ||
await balanceEvent; | ||
const balance = await spvwallet.getBalance(); | ||
assert.strictEqual(balance.confirmed, 1012345678); | ||
}); | ||
|
||
it('should handle a reorg', async () => { | ||
assert.strictEqual(chain.height, 101); | ||
|
||
// Main chain is ahead by 1 block now, catch the alt chain up | ||
const entry = await chain.getEntry(tip2.hash); | ||
const block1 = await miner.mineBlock(entry); | ||
await chain.add(block1); | ||
const entry1 = await chain.getEntry(block1.hash()); | ||
assert(entry1); | ||
|
||
// Tie game! | ||
assert.strictEqual(chain.height, entry1.height); | ||
|
||
// Now reorg main chain by adding a block to alt chain | ||
const block2 = await miner.mineBlock(entry1); | ||
assert(block2); | ||
|
||
const spvReorgedEvent = forEvent(spvnode, 'reorganize'); | ||
const spvResetEvent = forEvent(spvnode, 'reset'); | ||
let spvBlockEvents; | ||
|
||
let forked = false; | ||
let tipHash, competitorHash, forkHash; | ||
|
||
chain.once('reorganize', (tip, competitor, fork) => { | ||
// We will need to wait for competitor.height - fork.height blocks. | ||
spvBlockEvents = forEvent( | ||
spvnode, | ||
'block', | ||
competitor.height - fork.height | ||
); | ||
|
||
tipHash = tip.hash; | ||
competitorHash = competitor.hash; | ||
forkHash = fork.hash; | ||
|
||
forked = true; | ||
}); | ||
|
||
await chain.add(block2); | ||
|
||
assert(forked); | ||
assert.bufferEqual(chain.tip.hash, block2.hash()); | ||
assert(chain.tip.chainwork.gt(tip1.chainwork)); | ||
|
||
// Wait for all events. | ||
const [reorgs, resets, blocks] = await Promise.all([ | ||
spvReorgedEvent, | ||
spvResetEvent, | ||
spvBlockEvents | ||
]); | ||
|
||
{ | ||
const [tip, competitor, fork] = reorgs[0].values; | ||
assert.bufferEqual(tip.hash, tipHash); | ||
assert.bufferEqual(competitor.hash, competitorHash); | ||
assert.bufferEqual(fork.hash, forkHash); | ||
} | ||
|
||
{ | ||
const [resetToEntry] = resets[0].values; | ||
assert.bufferEqual(resetToEntry.hash, forkHash); | ||
} | ||
|
||
{ | ||
const lastBlockHash = blocks.pop().values[0].hash(); | ||
assert.bufferEqual(lastBlockHash, node.chain.tip.hash); | ||
} | ||
|
||
assert.deepStrictEqual(node.chain.tip, spvnode.chain.tip); | ||
}); | ||
|
||
it('should mine a block after a reorg', async () => { | ||
const blockEvent = forEvent(spvnode, 'block'); | ||
const block = await miner.mineBlock(node.chain.tip); | ||
await chain.add(block); | ||
|
||
// Check SPV & Full nodes are in sync | ||
await blockEvent; | ||
|
||
assert.deepStrictEqual(node.chain.tip, spvnode.chain.tip); | ||
|
||
const entry = await chain.getEntry(block.hash()); | ||
assert(entry); | ||
assert.bufferEqual(chain.tip.hash, entry.hash); | ||
|
||
const result = await chain.isMainChain(entry); | ||
assert(result); | ||
}); | ||
|
||
it('should unconfirm tx after reorg', async () => { | ||
const balance = await spvwallet.getBalance(); | ||
assert.strictEqual(balance.unconfirmed, 1012345678); | ||
}); | ||
}); |
Oops, something went wrong.