diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index f0bdce032c..c328905ac5 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -53,7 +53,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest] node: [12.x, 14.x, 16.x, 18.x] steps: diff --git a/docs/release-notes/release-notes-v4.x-draft.md b/docs/release-notes/release-notes-v4.x-draft.md index 9afff0757d..4c5599f9ac 100644 --- a/docs/release-notes/release-notes-v4.x-draft.md +++ b/docs/release-notes/release-notes-v4.x-draft.md @@ -62,6 +62,7 @@ v4.x Release notes - \[[`03a306b9`](https://github.com/handshake-org/hsd/commit/03a306b9)] - [#734](https://github.com/handshake-org/hsd/pull/734) - **SEMVER-MINOR protocol**: add checkpoint at height 100000 (Dec 2021) (@pinheadmz - Matthew Zipkin) - \[[`febc91c8`](https://github.com/handshake-org/hsd/commit/febc91c8)] - [#707](https://github.com/handshake-org/hsd/pull/707) - **SEMVER-MINOR wallet**: emit 'block connect' events. (@rithvikvibhu - Rithvik Vibhu) - \[[`dfccf4ef`](https://github.com/handshake-org/hsd/commit/dfccf4ef)] - [#650](https://github.com/handshake-org/hsd/pull/650) - **SEMVER-MINOR chain**: Gracefully shut down node on critical errors like full disk. (@pinheadmz - Matthew Zipkin & @nodech - Nodari Chkuaselidze) + - \[[`7822f572`](https://github.com/handshake-org/hsd/commit/7822f572)] - [#743](https://github.com/handshake-org/hsd/pull/743) - **SEMVER-MAJOR chain**: Continue compaction on restart if node quit. (@nodech - Nodari Chkuaselidze) - \[[`925db38a`](https://github.com/handshake-org/hsd/commit/925db38a)] - [#735](https://github.com/handshake-org/hsd/pull/735) - **ci**: add macos to the test matrix. (@nodech - Nodari Chkuaselidze) - \[[`e33ed104`](https://github.com/handshake-org/hsd/commit/e33ed104)] - [#733](https://github.com/handshake-org/hsd/pull/733) - **net**: update seeds. (@pinheadmz - Matthew Zipkin) - \[[`e77546c9`](https://github.com/handshake-org/hsd/commit/e77546c9)] - [#710](https://github.com/handshake-org/hsd/pull/710) - **net**: propagate the user agent from node to pool. (@Falci - Fernando Falci & @pinheadmz - Matthew Zipkin) @@ -93,3 +94,4 @@ v4.x Release notes - \[[`3b83199e`](https://github.com/handshake-org/hsd/commit/3b83199e)] - [#653](https://github.com/handshake-org/hsd/pull/653) - **test**: sighash_noinput is not implemented correctly. (@pinheadmz - Matthew Zipkin) - \[[`e8c9632c`](https://github.com/handshake-org/hsd/commit/e8c9632c)] - [#652](https://github.com/handshake-org/hsd/pull/652) - **dockerfile**: upgrade use node v14. (@skottler - Sam Kottler) - \[[`37731e63`](https://github.com/handshake-org/hsd/commit/37731e63)] - [#640](https://github.com/handshake-org/hsd/pull/640) - **ci**: Add node v16 to ci matrix, and remove v10. (@Anunayj - Anunay Jain) + - \[[`7c00f019`](https://github.com/handshake-org/hsd/commit/7c00f019)] - [#742](https://github.com/handshake-org/hsd/pull/742) - **test**: Update urkel and fix grindName tests. (@nodech - Nodari Chkuaselidze) diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index f744096ced..773ce37ecf 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -995,7 +995,6 @@ class ChainDB { // If the tree data gets out of sync or corrupted // the chain database knows where to resync the tree from. this.start(); - this.pendingTreeState.compact(entry.treeRoot, entry.height); // Note: the tree root commit height is always one block before its // appearence in a header. @@ -1031,6 +1030,15 @@ class ChainDB { // Reset in-memory tree delta this.txn = this.tree.txn(); + + // Mark tree compaction complete + this.start(); + this.pendingTreeState.compact(entry.treeRoot, entry.height); + this.put(layout.s.encode(), this.pendingTreeState.commit( + entry.treeRoot, + entry.height - 1 + )); + await this.commit(); } /** diff --git a/package-lock.json b/package-lock.json index e14a895887..adf9c4a4e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hsd", - "version": "4.0.0-rc.1", + "version": "4.0.0-rc.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "hsd", - "version": "4.0.0-rc.1", + "version": "4.0.0-rc.2", "license": "MIT", "dependencies": { "bcfg": "~0.1.7", @@ -35,7 +35,7 @@ "goosig": "~0.10.0", "hs-client": "~0.0.11", "n64": "~0.2.10", - "urkel": "~1.0.1" + "urkel": "~1.0.2" }, "bin": { "hs-seeder": "bin/hs-seeder", @@ -461,9 +461,9 @@ } }, "node_modules/urkel": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/urkel/-/urkel-1.0.1.tgz", - "integrity": "sha512-/ul3w/hvvGzppHqdpDAcEFe8kS1hi6ty5h7oQalIlVLwPNUJ/tz/h7KtIyNRC0+u7ANryq2Aw96N8snq+VYEOg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/urkel/-/urkel-1.0.2.tgz", + "integrity": "sha512-Y5UXbgBr6pczrD08N0SYJkWjtdtTTpmZsOvuftdrEHLnTjuxwSNjKsXYLQkICTptvnHAJ2OjI6XdAxtYTyOHew==", "dependencies": { "bfile": "~0.2.1", "bmutex": "~0.1.6", @@ -748,9 +748,9 @@ } }, "urkel": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/urkel/-/urkel-1.0.1.tgz", - "integrity": "sha512-/ul3w/hvvGzppHqdpDAcEFe8kS1hi6ty5h7oQalIlVLwPNUJ/tz/h7KtIyNRC0+u7ANryq2Aw96N8snq+VYEOg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/urkel/-/urkel-1.0.2.tgz", + "integrity": "sha512-Y5UXbgBr6pczrD08N0SYJkWjtdtTTpmZsOvuftdrEHLnTjuxwSNjKsXYLQkICTptvnHAJ2OjI6XdAxtYTyOHew==", "requires": { "bfile": "~0.2.1", "bmutex": "~0.1.6", diff --git a/package.json b/package.json index 320f6589a7..c90f251630 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hsd", - "version": "4.0.0-rc.1", + "version": "4.0.0-rc.2", "description": "Cryptocurrency bike-shed", "license": "MIT", "repository": "git://github.com/handshake-org/hsd.git", @@ -46,7 +46,7 @@ "goosig": "~0.10.0", "hs-client": "~0.0.11", "n64": "~0.2.10", - "urkel": "~1.0.1" + "urkel": "~1.0.2" }, "devDependencies": { "bmocha": "^2.1.6" diff --git a/test/anyone-can-renew-test.js b/test/anyone-can-renew-test.js index 76fa0149b0..b0573928d3 100644 --- a/test/anyone-can-renew-test.js +++ b/test/anyone-can-renew-test.js @@ -42,7 +42,7 @@ const {wdb} = node.require('walletdb'); let alice, aliceReceive; let bob, bobReceive; -const name = rules.grindName(5, 1, network); +const name = rules.grindName(10, 1, network); const nameHash = rules.hashName(name); let heightBeforeOpen, heightBeforeRegister, heightBeforeFinalize; let coin; diff --git a/test/auction-reorg-test.js b/test/auction-reorg-test.js index 98016b669f..5969c1d6b1 100644 --- a/test/auction-reorg-test.js +++ b/test/auction-reorg-test.js @@ -15,8 +15,9 @@ const ownership = require('../lib/covenants/ownership'); const network = Network.get('regtest'); const {treeInterval} = network.names; -const NAME1 = rules.grindName(10, 20, network); -const NAME2 = rules.grindName(10, 20, network); +const GNAME_SIZE = 10; +const NAME1 = rules.grindName(GNAME_SIZE, 20, network); +const NAME2 = rules.grindName(GNAME_SIZE, 20, network); const workers = new WorkerPool({ // Must be disabled for `ownership.ignore`. diff --git a/test/auction-rpc-test.js b/test/auction-rpc-test.js index 8e9c117a57..b111a339b6 100644 --- a/test/auction-rpc-test.js +++ b/test/auction-rpc-test.js @@ -136,11 +136,13 @@ class TestUtil { } } +const GNAME_SIZE = 10; + describe('Auction RPCs', function() { this.timeout(60000); const util = new TestUtil(); - const name = rules.grindName(2, 0, Network.get('regtest')); + const name = rules.grindName(GNAME_SIZE, 0, Network.get('regtest')); let winner, loser; const winnerBid = { bid: 5, diff --git a/test/auction-test.js b/test/auction-test.js index a94e3ded3c..a224d9ad31 100644 --- a/test/auction-test.js +++ b/test/auction-test.js @@ -11,8 +11,9 @@ const rules = require('../lib/covenants/rules'); const ownership = require('../lib/covenants/ownership'); const network = Network.get('regtest'); -const NAME1 = rules.grindName(10, 20, network); -const NAME2 = rules.grindName(10, 20, network); +const GNAME_SIZE = 10; +const NAME1 = rules.grindName(GNAME_SIZE, 20, network); +const NAME2 = rules.grindName(GNAME_SIZE, 20, network); const workers = new WorkerPool({ // Must be disabled for `ownership.ignore`. diff --git a/test/chain-checkpoints-test.js b/test/chain-checkpoints-test.js index 1997739656..124fa59b38 100644 --- a/test/chain-checkpoints-test.js +++ b/test/chain-checkpoints-test.js @@ -43,6 +43,8 @@ const wallet = new MemWallet({ network }); +const GNAME_SIZE = 10; + wallet.getNameStatus = async (nameHash) => { assert(Buffer.isBuffer(nameHash)); const height = chainGenerator.height + 1; @@ -155,11 +157,11 @@ describe('Checkpoints', function() { it('should win names in auction', async () => { // Only one bid, 0-value name - const name1 = rules.grindName(5, chainGenerator.height - 5, network); + const name1 = rules.grindName(GNAME_SIZE, chainGenerator.height - 5, network); // Two bids, name will have a value - const name2 = rules.grindName(5, chainGenerator.height - 5, network); + const name2 = rules.grindName(GNAME_SIZE, chainGenerator.height - 5, network); // Two bids, but wallet will not REGISTER - const name3 = rules.grindName(5, chainGenerator.height - 5, network); + const name3 = rules.grindName(GNAME_SIZE, chainGenerator.height - 5, network); const open1 = await wallet.sendOpen(name1); const open2 = await wallet.sendOpen(name2); @@ -218,7 +220,7 @@ describe('Checkpoints', function() { }); it('should bid in multiple blocks', async () => { - const name = rules.grindName(5, chainGenerator.height - 5, network); + const name = rules.grindName(GNAME_SIZE, chainGenerator.height - 5, network); const open = await wallet.sendOpen(name); @@ -366,7 +368,7 @@ describe('Checkpoints', function() { let invalidBlockEntry; before(async () => { - name = rules.grindName(5, chainGenerator.height - 5, network); + name = rules.grindName(GNAME_SIZE, chainGenerator.height - 5, network); }); after(async () => { diff --git a/test/chain-tree-compaction-test.js b/test/chain-tree-compaction-test.js index a2e4f2bcd5..84ac74bb17 100644 --- a/test/chain-tree-compaction-test.js +++ b/test/chain-tree-compaction-test.js @@ -24,6 +24,8 @@ const { revealPeriod } = network.names; +const GNAME_SIZE = 10; + describe('Tree Compacting', function() { const oldKeepBlocks = network.block.keepBlocks; const oldpruneAfterHeight = network.block.pruneAfterHeight; @@ -182,7 +184,7 @@ describe('Tree Compacting', function() { }); it('should win an auction and register', async () => { - name = rules.grindName(3, chain.height, network); + name = rules.grindName(GNAME_SIZE, chain.height, network); nameHash = rules.hashName(name); send(await wallet.sendOpen(name), mempool); await mineBlocks(treeInterval + 1, mempool); @@ -761,6 +763,81 @@ describe('Tree Compacting', function() { await node.close(); }); + it('should continue compaction on restart if it did not finish', async () => { + const {keepBlocks} = network.block; + const compactInterval = keepBlocks; + const nodeOptions = { + prefix, + network: 'regtest', + memory: false, + compactTreeOnInit: true, + compactTreeInitInterval: compactInterval + }; + + node = new FullNode(nodeOptions); + + await node.ensure(); + let compacted = false; + const compactWrapper = (node) => { + const compactTree = node.chain.compactTree.bind(node.chain); + + compacted = false; + node.chain.compactTree = () => { + compacted = true; + return compactTree(); + }; + }; + + compactWrapper(node); + await node.open(); + assert.strictEqual(compacted, false); + + // get enough blocks for the compaction check. + const blocks = compactInterval + keepBlocks + 1; + const waiter = forEventCondition(node, 'connect', e => e.height >= blocks); + + await node.rpc.generateToAddress( + [blocks, new Address().toString('regtest')] + ); + + await waiter; + await node.close(); + + // Should try compact, because we have enough blocks. + // but we make sure it fails. + node = new FullNode(nodeOptions); + compactWrapper(node); + node.chain.db.tree.compact = () => { + throw new Error('STOP'); + }; + + let err; + try { + await node.open(); + } catch (e) { + err = e; + } + + assert(err); + assert.strictEqual(err.message, 'Critical Error: STOP'); + assert.strictEqual(compacted, true); + + try { + await node.blocks.close(); + await node.chain.close(); + await node.close(); + } catch (e) { + ; + } + + // It should retry compaction on restart. + node = new FullNode(nodeOptions); + compactWrapper(node); + + await node.open(); + assert.strictEqual(compacted, true); + }); + it('should recompact tree if tree init interval passed', async () => { const {keepBlocks} = network.block; const compactInterval = keepBlocks; @@ -1004,7 +1081,7 @@ describe('Tree Compacting', function() { // This ensures that every single block results in a different // tree and treeRoot without any auctions. if (open) { - const name = rules.grindName(4, chain.height - 1, network); + const name = rules.grindName(GNAME_SIZE, chain.height - 1, network); const tx = await wallet.sendOpen(name); job.pushTX(tx.toTX()); job.refresh(); diff --git a/test/interactive-swap-test.js b/test/interactive-swap-test.js index 0fdbaf8345..0e5adb8d6f 100644 --- a/test/interactive-swap-test.js +++ b/test/interactive-swap-test.js @@ -38,7 +38,7 @@ let alice, bob, aliceReceive, bobReceive; let aliceOriginalBalance, bobOriginalBalance, bobFee; // These are data that will be communicated between Alice and Bob -const name = rules.grindName(5, 1, network); +const name = rules.grindName(10, 1, network); const nameHash = rules.hashName(name); const price = 1234567; // 1.234567 HNS let blob; diff --git a/test/mempool-test.js b/test/mempool-test.js index f0d868170c..419022119f 100644 --- a/test/mempool-test.js +++ b/test/mempool-test.js @@ -848,7 +848,7 @@ describe('Mempool', function() { const addr = chaincoins.createReceive().getAddress(); open.addOutput(addr, 90000); - const name = rules.grindName(5, 0, mempool.network); + const name = rules.grindName(10, 0, mempool.network); const rawName = Buffer.from(name, 'ascii'); const nameHash = rules.hashName(rawName); open.outputs[0].covenant.type = types.OPEN; diff --git a/test/wallet-accounts-auction-test.js b/test/wallet-accounts-auction-test.js index bee7200029..67e1996c03 100644 --- a/test/wallet-accounts-auction-test.js +++ b/test/wallet-accounts-auction-test.js @@ -27,7 +27,7 @@ const wclient = new WalletClient({ const {wdb} = node.require('walletdb'); -const name = rules.grindName(5, 1, network); +const name = rules.grindName(10, 1, network); let wallet, alice, bob, aliceReceive, bobReceive; async function mineBlocks(n, addr) { diff --git a/test/wallet-auction-test.js b/test/wallet-auction-test.js index a0fe689efd..2d09d262a6 100644 --- a/test/wallet-auction-test.js +++ b/test/wallet-auction-test.js @@ -12,7 +12,7 @@ const rules = require('../lib/covenants/rules'); const Address = require('../lib/primitives/address'); const network = Network.get('regtest'); -const NAME1 = rules.grindName(5, 2, network); +const NAME1 = rules.grindName(10, 2, network); const { treeInterval, biddingPeriod, diff --git a/test/wallet-importname-test.js b/test/wallet-importname-test.js index 0322f9e9e4..0ace713e1d 100644 --- a/test/wallet-importname-test.js +++ b/test/wallet-importname-test.js @@ -30,9 +30,10 @@ const wclient = new WalletClient({ const {wdb} = node.require('walletdb'); -const name = rules.grindName(5, 1, network); +const GNAME_SIZE = 10; +const name = rules.grindName(GNAME_SIZE, 1, network); const nameHash = rules.hashName(name); -const wrongName = rules.grindName(5, 1, network); +const wrongName = rules.grindName(GNAME_SIZE, 1, network); const wrongNameHash = rules.hashName(wrongName); let alice, bob, aliceReceive, bobReceive; @@ -232,9 +233,9 @@ describe('Wallet Import Name', function() { }); describe('import multiple / overlapping names', function() { - const name1 = rules.grindName(4, 1, network); - const name2 = rules.grindName(5, 1, network); - const name3 = rules.grindName(6, 1, network); + const name1 = rules.grindName(GNAME_SIZE, 1, network); + const name2 = rules.grindName(GNAME_SIZE, 1, network); + const name3 = rules.grindName(GNAME_SIZE, 1, network); let startHeight; it('should open and bid from Alice\'s wallet', async () => { diff --git a/test/wallet-rescan-test.js b/test/wallet-rescan-test.js index 283116ffa6..e6eef13cf6 100644 --- a/test/wallet-rescan-test.js +++ b/test/wallet-rescan-test.js @@ -18,6 +18,8 @@ const { transferLockup } = network.names; +const GNAME_SIZE = 10; + describe('Wallet rescan with namestate transitions', function() { describe('Only sends OPEN', function() { // Bob runs a full node with wallet plugin @@ -49,7 +51,7 @@ describe('Wallet rescan with namestate transitions', function() { return node.chain.db.getNameStatus(nameHash, height, hardened); }; - const NAME = rules.grindName(4, 4, network); + const NAME = rules.grindName(GNAME_SIZE, 4, network); // Hash of the FINALIZE transaction let aliceFinalizeHash; @@ -303,7 +305,7 @@ describe('Wallet rescan with namestate transitions', function() { return node.chain.db.getNameStatus(nameHash, height, hardened); }; - const NAME = rules.grindName(4, 4, network); + const NAME = rules.grindName(GNAME_SIZE, 4, network); // Block that confirmed the bids let bidBlockHash; diff --git a/test/wallet-rpc-test.js b/test/wallet-rpc-test.js index 6053bd4733..660c30b191 100644 --- a/test/wallet-rpc-test.js +++ b/test/wallet-rpc-test.js @@ -58,6 +58,8 @@ const wclient = new WalletClient({ const {wdb} = node.require('walletdb'); +const GNAME_SIZE = 10; + describe('Wallet RPC Methods', function() { this.timeout(15000); @@ -364,8 +366,8 @@ describe('Wallet RPC Methods', function() { }); describe('signmessagewithname & verifymessagewithname', () => { - const name = rules.grindName(5, 1, network); - const nonWalletName = rules.grindName(5, 1, network); + const name = rules.grindName(GNAME_SIZE, 1, network); + const nonWalletName = rules.grindName(GNAME_SIZE, 1, network); const message = 'Decentralized naming and certificate authority'; const invalidNames = ['', null, '\'null\'', 'localhost']; @@ -596,8 +598,8 @@ describe('Wallet RPC Methods', function() { }); it('should do an auction', async () => { - const NAME1 = rules.grindName(5, 2, network); - const NAME2 = rules.grindName(6, 3, network); + const NAME1 = rules.grindName(GNAME_SIZE, 2, network); + const NAME2 = rules.grindName(GNAME_SIZE, 3, network); const addr = await wclient.execute('getnewaddress', []); await nclient.execute('generatetoaddress', [10, addr]); await forValue(wdb, 'height', node.chain.height);