From e69f6a280faf8a243fcf2eb1205fabde00d9df3b Mon Sep 17 00:00:00 2001 From: Nodari Chkuaselidze Date: Sat, 6 Nov 2021 19:34:40 +0400 Subject: [PATCH 1/4] wallet: minor record serialization fixes. --- lib/wallet/records.js | 29 ++++++++++++++--------------- lib/wallet/walletdb.js | 6 +----- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/lib/wallet/records.js b/lib/wallet/records.js index 88d1bbfb..a81ccb8a 100644 --- a/lib/wallet/records.js +++ b/lib/wallet/records.js @@ -139,19 +139,6 @@ class BlockMeta extends bio.Struct { return this; } - /** - * Instantiate block meta from json object. - * @private - * @param {Object} json - */ - - fromJSON(json) { - this.hash = json.hash; - this.height = json.height; - this.time = json.time; - return this; - } - /** * Instantiate block meta from serialized tip data. * @private @@ -181,7 +168,7 @@ class BlockMeta extends bio.Struct { */ getSize() { - return 42; + return 40; } /** @@ -196,6 +183,18 @@ class BlockMeta extends bio.Struct { return bw; } + /** + * Instantiate block meta from json object. + * @param {Object} json + */ + + fromJSON(json) { + this.hash = util.parseHex(json.hash, 32); + this.height = json.height; + this.time = json.time; + return this; + } + /** * Convert the block meta to a more json-friendly object. * @returns {Object} @@ -203,7 +202,7 @@ class BlockMeta extends bio.Struct { getJSON() { return { - hash: this.hash, + hash: this.hash.toString('hex'), height: this.height, time: this.time }; diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 89aedb61..95a0e40b 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -2115,11 +2115,7 @@ class WalletDB extends EventEmitter { if (!hash) return null; - const block = new BlockMeta(); - block.hash = hash; - block.height = height; - - return block; + return new BlockMeta(hash, height); } /** From 64a647530b53779957818771adb10fadf68b7aca Mon Sep 17 00:00:00 2001 From: Nodari Chkuaselidze Date: Sat, 6 Nov 2021 19:36:38 +0400 Subject: [PATCH 2/4] test: add wallet record - ChainState and BlockMeta tests. --- test/wallet-records-test.js | 167 ++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 test/wallet-records-test.js diff --git a/test/wallet-records-test.js b/test/wallet-records-test.js new file mode 100644 index 00000000..ccfe8c5f --- /dev/null +++ b/test/wallet-records-test.js @@ -0,0 +1,167 @@ +'use strict'; + +const assert = require('bsert'); +const random = require('bcrypto/lib/random'); +const consensus = require('../lib/protocol/consensus'); +const Records = require('../lib/wallet/records'); +const ChainEntry = require('../lib/blockchain/chainentry'); +const { + ChainState, + BlockMeta +} = Records; + +describe('Wallet Records', function() { + describe('ChainState', function() { + function check(state, expected) { + assert.strictEqual(state.startHeight, expected.startHeight); + assert.bufferEqual(state.startHash, expected.startHash); + assert.strictEqual(state.height, expected.height); + assert.strictEqual(state.marked, expected.marked); + }; + + function getRandomChainState(marked = false) { + return { + startHeight: random.randomInt(), + startHash: random.randomBytes(32), + height: random.randomInt(), + marked: marked + }; + } + it('should encode/decode with defaults', () => { + const state = new ChainState(); + const encoded = state.encode(); + const decoded = ChainState.decode(encoded); + + assert.deepStrictEqual(state, decoded); + + check(state, { + startHeight: 0, + startHash: consensus.ZERO_HASH, + height: 0, + marked: false + }); + }); + + it('should inject data', () => { + const data1 = getRandomChainState(false); + const data2 = getRandomChainState(true); + + let state = new ChainState(); + state.inject(data1); + check(state, data1); + + state = new ChainState(); + state.inject(data2); + check(state, data2); + }); + + it('should encode/decode', () => { + const data = getRandomChainState(false); + const state = new ChainState(); + state.inject(data); + + const encoded = state.encode(); + const decoded = ChainState.decode(encoded); + + check(decoded, state); + }); + + it('should encode/decode with marked true', () => { + const data = getRandomChainState(true); + const state = new ChainState(); + state.inject(data); + + const encoded = state.encode(); + const decoded = ChainState.decode(encoded); + + check(decoded, state); + }); + }); + + describe('BlockMeta', function() { + function check(actual, expected) { + assert.bufferEqual(actual.hash, expected.hash); + assert.strictEqual(actual.height, expected.height); + assert.strictEqual(actual.time, expected.time); + } + + function getRandomBlockMetaData() { + return { + hash: random.randomBytes(32), + height: random.randomInt(), + time: random.randomInt() + }; + } + + it('should initialize with proper defaults', () => { + const meta = new BlockMeta(); + + check(meta, { + hash: consensus.ZERO_HASH, + height: -1, + time: 0 + }); + }); + + it('should initialize with params', () => { + const data = getRandomBlockMetaData(); + const meta = new BlockMeta(data.hash, data.height, data.time); + check(meta, data); + }); + + it('should inject data', () => { + const data = getRandomBlockMetaData(); + const meta = new BlockMeta(); + meta.inject(data); + + check(meta, data); + }); + + it('should encode/decode', () => { + const data = getRandomBlockMetaData(); + const meta = new BlockMeta(); + meta.inject(data); + + const encoded = meta.encode(); + const decoded = BlockMeta.decode(encoded); + + check(meta, data); + check(decoded, meta); + }); + + it('should JSON encode/decode', () => { + const data = getRandomBlockMetaData(); + const meta = new BlockMeta(); + meta.inject(data); + + const encoded = meta.toJSON(); + const decoded = BlockMeta.fromJSON(encoded); + + assert.deepStrictEqual(encoded, { + hash: data.hash.toString('hex'), + height: data.height, + time: data.time + }); + check(decoded, data); + }); + + it('should return block hash', () => { + const data = getRandomBlockMetaData(); + const meta = new BlockMeta(); + meta.inject(data); + + assert.bufferEqual(meta.toHash(), data.hash); + }); + + it('should be created from ChainEntry', () => { + const data = getRandomBlockMetaData(); + const entry = new ChainEntry(); + entry.hash = data.hash; + entry.height = data.height; + entry.time = data.time; + + const meta = BlockMeta.fromEntry(entry); + check(meta, data); + }); + }); +}); From df1e8f22ebb2de43573c8c8c1a4367df4f50f15d Mon Sep 17 00:00:00 2001 From: Nodari Chkuaselidze Date: Wed, 10 Nov 2021 16:58:13 +0400 Subject: [PATCH 3/4] test: add wallet TXRecord tests. --- test/wallet-records-test.js | 275 ++++++++++++++++++++++++++++++------ 1 file changed, 232 insertions(+), 43 deletions(-) diff --git a/test/wallet-records-test.js b/test/wallet-records-test.js index ccfe8c5f..51ae2ced 100644 --- a/test/wallet-records-test.js +++ b/test/wallet-records-test.js @@ -5,28 +5,119 @@ const random = require('bcrypto/lib/random'); const consensus = require('../lib/protocol/consensus'); const Records = require('../lib/wallet/records'); const ChainEntry = require('../lib/blockchain/chainentry'); +const TX = require('../lib/primitives/tx'); + const { ChainState, - BlockMeta + BlockMeta, + TXRecord } = Records; +function getRandomChainState(marked = false) { + return { + startHeight: random.randomInt(), + startHash: random.randomBytes(32), + height: random.randomInt(), + marked: marked + }; +} + +function getRandomBlockMetaData() { + return { + hash: random.randomBytes(32), + height: random.randomInt(), + time: random.randomInt() + }; +} + +function getRandomTX() { + return new TX({ + inputs: [{ + prevout: { + hash: random.randomBytes(32), + index: random.randomRange(0, 100) + } + }], + outputs: [{ + value: random.randomInt() + }] + }); +} + +function getRandomTXRecordData(genTX = false, genBlock = false) { + let block, tx; + + if (genBlock) { + const blockData = getRandomBlockMetaData(); + block = new BlockMeta(blockData.hash, blockData.height, blockData.time); + } + + if (genTX) + tx = getRandomTX(); + + const data = { + height: block ? block.height : -1, + time: block ? block.time : 0, + block: block ? block.hash : null, + tx: tx, + hash: tx ? tx.hash() : null + }; + + return {data, tx, block}; +} + +/* + * These don't expect actual instances of these classes + * just object property checks. + */ + +function compareChainState(state, expected) { + assert.strictEqual(state.startHeight, expected.startHeight); + assert.bufferEqual(state.startHash, expected.startHash); + assert.strictEqual(state.height, expected.height); + assert.strictEqual(state.marked, expected.marked); +}; + +function compareBlockMeta(actual, expected) { + assert.bufferEqual(actual.hash, expected.hash); + assert.strictEqual(actual.height, expected.height); + assert.strictEqual(actual.time, expected.time); +} + +function compareTXRecord(actual, expected) { + if (actual.tx == null) { + // defaults + assert.strictEqual(actual.hash, null); + + // expected should be the same (otherwise it's a bug in the test). + assert.strictEqual(actual.tx, expected.tx); + assert.strictEqual(actual.hash, expected.hash); + } else { + assert.bufferEqual(actual.hash, expected.hash); + assert.bufferEqual(actual.tx.encode(), expected.tx.encode()); + } + + if (actual.block == null) { + // same as actual.tx checks + assert.strictEqual(actual.height, -1); + assert.strictEqual(actual.time, 0); + + assert.strictEqual(actual.block, expected.block); + assert.strictEqual(actual.height, expected.height); + assert.strictEqual(actual.time, expected.time); + } else { + assert.bufferEqual(actual.block, expected.block); + assert.strictEqual(actual.height, expected.height); + assert.strictEqual(actual.time, expected.time); + } + + assert(typeof actual.mtime === 'number'); + assert(actual.mtime > 0); + assert.strictEqual(actual.index, -1); +} + describe('Wallet Records', function() { describe('ChainState', function() { - function check(state, expected) { - assert.strictEqual(state.startHeight, expected.startHeight); - assert.bufferEqual(state.startHash, expected.startHash); - assert.strictEqual(state.height, expected.height); - assert.strictEqual(state.marked, expected.marked); - }; - - function getRandomChainState(marked = false) { - return { - startHeight: random.randomInt(), - startHash: random.randomBytes(32), - height: random.randomInt(), - marked: marked - }; - } it('should encode/decode with defaults', () => { const state = new ChainState(); const encoded = state.encode(); @@ -34,7 +125,7 @@ describe('Wallet Records', function() { assert.deepStrictEqual(state, decoded); - check(state, { + compareChainState(state, { startHeight: 0, startHash: consensus.ZERO_HASH, height: 0, @@ -48,11 +139,11 @@ describe('Wallet Records', function() { let state = new ChainState(); state.inject(data1); - check(state, data1); + compareChainState(state, data1); state = new ChainState(); state.inject(data2); - check(state, data2); + compareChainState(state, data2); }); it('should encode/decode', () => { @@ -63,7 +154,7 @@ describe('Wallet Records', function() { const encoded = state.encode(); const decoded = ChainState.decode(encoded); - check(decoded, state); + compareChainState(decoded, state); }); it('should encode/decode with marked true', () => { @@ -74,29 +165,15 @@ describe('Wallet Records', function() { const encoded = state.encode(); const decoded = ChainState.decode(encoded); - check(decoded, state); + compareChainState(decoded, state); }); }); describe('BlockMeta', function() { - function check(actual, expected) { - assert.bufferEqual(actual.hash, expected.hash); - assert.strictEqual(actual.height, expected.height); - assert.strictEqual(actual.time, expected.time); - } - - function getRandomBlockMetaData() { - return { - hash: random.randomBytes(32), - height: random.randomInt(), - time: random.randomInt() - }; - } - - it('should initialize with proper defaults', () => { + it('should initialize with defaults', () => { const meta = new BlockMeta(); - check(meta, { + compareBlockMeta(meta, { hash: consensus.ZERO_HASH, height: -1, time: 0 @@ -106,7 +183,7 @@ describe('Wallet Records', function() { it('should initialize with params', () => { const data = getRandomBlockMetaData(); const meta = new BlockMeta(data.hash, data.height, data.time); - check(meta, data); + compareBlockMeta(meta, data); }); it('should inject data', () => { @@ -114,7 +191,7 @@ describe('Wallet Records', function() { const meta = new BlockMeta(); meta.inject(data); - check(meta, data); + compareBlockMeta(meta, data); }); it('should encode/decode', () => { @@ -125,8 +202,8 @@ describe('Wallet Records', function() { const encoded = meta.encode(); const decoded = BlockMeta.decode(encoded); - check(meta, data); - check(decoded, meta); + compareBlockMeta(meta, data); + compareBlockMeta(decoded, meta); }); it('should JSON encode/decode', () => { @@ -142,7 +219,7 @@ describe('Wallet Records', function() { height: data.height, time: data.time }); - check(decoded, data); + compareBlockMeta(decoded, data); }); it('should return block hash', () => { @@ -161,7 +238,119 @@ describe('Wallet Records', function() { entry.time = data.time; const meta = BlockMeta.fromEntry(entry); - check(meta, data); + compareBlockMeta(meta, data); + }); + }); + + describe('TXRecord', function() { + const emptyBlock = { + height: -1, + block: null, + time: 0 + }; + + const emptyTX = { + tx: null, + hash: null + }; + + it('should initialize with defaults', () => { + const wtx = new TXRecord(); + const mtime = wtx.mtime; + + compareTXRecord(wtx, { + mtime: mtime, + index: -1, + ...emptyTX, + ...emptyBlock, + }); + }); + + it('should initialize w/ tx', () => { + const {data, tx} = getRandomTXRecordData(true); + const wtx = new TXRecord(tx); + + compareTXRecord(wtx, data); + }); + + it('should initialize w/ tx and block', () => { + const {data, block, tx} = getRandomTXRecordData(true, true) + const wtx = new TXRecord(tx, block); + + compareTXRecord(wtx, data); + }); + + it('should fail encode w/o tx', () => { + let err; + try { + const wtx = new TXRecord(); + wtx.encode(); + } catch (e) { + err = e; + } + + assert(err, 'Should throw error w/o tx'); + }); + + it('should encode/decode w/ tx', () => { + const {data, tx} = getRandomTXRecordData(true); + + const wtx = new TXRecord(tx); + const encoded = wtx.encode(); + const decoded = TXRecord.decode(encoded); + + compareTXRecord(wtx, data); + compareTXRecord(decoded, data); + }); + + it('should encode/decode w/ tx and block', () => { + const {data, tx, block} = getRandomTXRecordData(true, true); + + const wtx = new TXRecord(tx, block); + const encoded = wtx.encode(); + const decoded = TXRecord.decode(encoded); + + compareTXRecord(wtx, data); + compareTXRecord(decoded, data); + }); + + it('should initialize from TX', () => { + const {data, tx} = getRandomTXRecordData(true); + const wtx = TXRecord.fromTX(tx); + compareTXRecord(wtx, data); + }); + + it('should initialize from TX and Block', () => { + const {data, tx, block} = getRandomTXRecordData(true, true); + const wtx = TXRecord.fromTX(tx, block); + compareTXRecord(wtx, data); + }); + + it('should set and unset block', () => { + const {data, tx, block} = getRandomTXRecordData(true, true); + const wtx = TXRecord.fromTX(tx); + + assert.strictEqual(wtx.getBlock(), null); + assert.strictEqual(wtx.getDepth(random.randomInt()), 0); + compareTXRecord(wtx, { + ...data, + ...emptyBlock + }); + + wtx.setBlock(block); + assert.strictEqual(wtx.getDepth(0), 0); + assert.strictEqual(wtx.getDepth(block.height), 1); + assert.strictEqual(wtx.getDepth(block.height + 1000), 1001); + compareBlockMeta(wtx.getBlock(), block); + compareTXRecord(wtx, data); + + wtx.unsetBlock(block); + assert.strictEqual(wtx.getBlock(), null); + assert.strictEqual(wtx.getDepth(random.randomInt()), 0); + compareTXRecord(wtx, { + ...data, + ...emptyBlock + }); }); }); }); From 5f115324128df48d68dbde696664f91494656c07 Mon Sep 17 00:00:00 2001 From: Nodari Chkuaselidze Date: Wed, 10 Nov 2021 17:17:46 +0400 Subject: [PATCH 4/4] test: add wallet MapRecord tests. --- test/wallet-records-test.js | 75 +++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/test/wallet-records-test.js b/test/wallet-records-test.js index 51ae2ced..2d59c9ff 100644 --- a/test/wallet-records-test.js +++ b/test/wallet-records-test.js @@ -10,7 +10,8 @@ const TX = require('../lib/primitives/tx'); const { ChainState, BlockMeta, - TXRecord + TXRecord, + MapRecord } = Records; function getRandomChainState(marked = false) { @@ -262,7 +263,7 @@ describe('Wallet Records', function() { mtime: mtime, index: -1, ...emptyTX, - ...emptyBlock, + ...emptyBlock }); }); @@ -274,7 +275,7 @@ describe('Wallet Records', function() { }); it('should initialize w/ tx and block', () => { - const {data, block, tx} = getRandomTXRecordData(true, true) + const {data, block, tx} = getRandomTXRecordData(true, true); const wtx = new TXRecord(tx, block); compareTXRecord(wtx, data); @@ -353,4 +354,72 @@ describe('Wallet Records', function() { }); }); }); + + describe('MapRecord', function() { + it('should initialize with default', () => { + const map = new MapRecord(); + + assert.strictEqual(map.wids.size, 0); + }); + + it('should encode/decode empty map', () => { + const map = new MapRecord(); + const encoded = map.encode(); + const decoded = MapRecord.decode(encoded); + + assert.bufferEqual(encoded, Buffer.from('00'.repeat(4), 'hex')); + assert.strictEqual(map.wids.size, 0); + assert.strictEqual(decoded.wids.size, 0); + }); + + it('should encode/decode map', () => { + const rand = random.randomRange(1, 100); + const map = new MapRecord(); + + for (let i = 0; i < rand; i++) + map.add(i); + + const encoded = map.encode(); + const decoded = MapRecord.decode(encoded); + + assert.strictEqual(decoded.wids.size, map.wids.size); + for (let i = 0; i < rand; i++) + assert(decoded.wids.has(i)); + }); + + it('should add and remove items from the map', () => { + const items = 20; + const map = new MapRecord(); + + for (let i = 0; i < items; i++) { + const res = map.add(i); + assert.strictEqual(res, true); + assert.strictEqual(map.wids.size, i + 1); + } + + for (let i = 0; i < items; i++) { + const res = map.add(i); + assert.strictEqual(res, false); + assert.strictEqual(map.wids.size, items); + } + + for (let i = items; i < items * 2; i++) { + const res = map.remove(i); + assert.strictEqual(res, false); + assert.strictEqual(map.wids.size, items); + } + + for (let i = 0; i < items; i++) { + const res = map.remove(i); + assert.strictEqual(res, true); + assert.strictEqual(map.wids.size, items - i - 1); + } + + for (let i = 0; i < items; i++) { + const res = map.remove(i); + assert.strictEqual(res, false); + assert.strictEqual(map.wids.size, 0); + } + }); + }); });