From f08d5bb12854675ab12a77822152678971c5237a Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Thu, 11 Jan 2024 11:58:33 -0800 Subject: [PATCH] Hash block header with strategy class (#4533) --- ironfish/src/blockchain/blockchain.test.ts | 4 +- ironfish/src/blockchain/blockchain.ts | 6 +- ironfish/src/blockchain/database/headers.ts | 2 +- ironfish/src/consensus/verifier.test.ts | 54 ++++++++------- ironfish/src/genesis/addGenesisTransaction.ts | 4 +- ironfish/src/genesis/genesis.test.slow.ts | 4 +- ironfish/src/genesis/makeGenesisBlock.ts | 21 +++--- ironfish/src/mining/manager.test.slow.ts | 10 +-- ironfish/src/mining/manager.ts | 4 +- .../src/network/messages/getBlocks.test.ts | 6 +- .../network/messages/getCompactBlock.test.ts | 4 +- .../network/messages/newCompactBlock.test.ts | 6 +- ironfish/src/network/peerNetwork.test.ts | 4 +- ironfish/src/network/peerNetwork.ts | 10 +-- ironfish/src/primitives/block.test.ts | 22 +++--- ironfish/src/primitives/block.ts | 10 +-- ironfish/src/primitives/blockheader.test.ts | 68 ++++++++++++------- ironfish/src/primitives/blockheader.ts | 19 ++---- ironfish/src/serde/BlockTemplateSerde.ts | 6 +- ironfish/src/strategy.ts | 33 +++++++++ ironfish/src/testUtilities/fixtures/blocks.ts | 2 +- ironfish/src/utils/blockutil.test.ts | 7 +- 22 files changed, 176 insertions(+), 130 deletions(-) diff --git a/ironfish/src/blockchain/blockchain.test.ts b/ironfish/src/blockchain/blockchain.test.ts index 436c9ddef1..3e61957ea2 100644 --- a/ironfish/src/blockchain/blockchain.test.ts +++ b/ironfish/src/blockchain/blockchain.test.ts @@ -435,7 +435,7 @@ describe('Blockchain', () => { const genesis = nodeTest.chain.genesis expect(node.chain.head?.hash).toEqualBuffer(genesis.hash) - const blockB3Invalid = Block.fromRaw({ + const blockB3Invalid = nodeTest.strategy.newBlock({ header: { ...blockB3.header, noteCommitment: Buffer.alloc(32), @@ -731,7 +731,7 @@ describe('Blockchain', () => { valid: true, }) - const invalidBlock = Block.fromRaw({ + const invalidBlock = nodeTest.strategy.newBlock({ header: { ...block.header, timestamp: new Date(0), diff --git a/ironfish/src/blockchain/blockchain.ts b/ironfish/src/blockchain/blockchain.ts index 95b89c9d83..e3253de915 100644 --- a/ironfish/src/blockchain/blockchain.ts +++ b/ironfish/src/blockchain/blockchain.ts @@ -200,7 +200,7 @@ export class Blockchain { } private async seed() { - const genesis = BlockSerde.deserialize(this.seedGenesisBlock) + const genesis = BlockSerde.deserialize(this.seedGenesisBlock, this.strategy) const result = await this.addBlock(genesis) Assert.isTrue(result.isAdded, `Could not seed genesis: ${result.reason || 'unknown'}`) @@ -230,7 +230,7 @@ export class Blockchain { if (genesisHeader) { Assert.isTrue( genesisHeader.hash.equals( - BlockHeaderSerde.deserialize(this.seedGenesisBlock.header).hash, + BlockHeaderSerde.deserialize(this.seedGenesisBlock.header, this.strategy).hash, ), 'Genesis block in network definition does not match existing chain genesis block', ) @@ -948,7 +948,7 @@ export class Blockchain { graffiti, } - const header = new BlockHeader(rawHeader, noteSize, BigInt(0)) + const header = this.strategy.newBlockHeader(rawHeader, noteSize, BigInt(0)) const block = new Block(header, transactions) if (verifyBlock && !previousBlockHash.equals(GENESIS_BLOCK_PREVIOUS)) { diff --git a/ironfish/src/blockchain/database/headers.ts b/ironfish/src/blockchain/database/headers.ts index cfb32be823..21a186e562 100644 --- a/ironfish/src/blockchain/database/headers.ts +++ b/ironfish/src/blockchain/database/headers.ts @@ -63,7 +63,7 @@ export class HeaderEncoding implements IDatabaseEncoding { timestamp: new Date(timestamp), graffiti, } - const header = new BlockHeader(rawHeader, noteSize, work, hash) + const header = new BlockHeader(rawHeader, hash, noteSize, work) return { header } } diff --git a/ironfish/src/consensus/verifier.test.ts b/ironfish/src/consensus/verifier.test.ts index aa6c753b49..91f8764785 100644 --- a/ironfish/src/consensus/verifier.test.ts +++ b/ironfish/src/consensus/verifier.test.ts @@ -199,7 +199,7 @@ describe('Verifier', () => { const { block } = await useBlockWithTx(nodeTest.node) const reorderedTransactions = [block.transactions[1], block.transactions[0]] - const invalidBlock = Block.fromRaw({ + const invalidBlock = nodeTest.strategy.newBlock({ header: { ...block.header, transactionCommitment: transactionCommitment(reorderedTransactions), @@ -219,7 +219,7 @@ describe('Verifier', () => { const minersFee = await useMinersTxFixture(nodeTest.node, account, undefined, 0) const reorderedTransactions = [block.transactions[0], minersFee] - const invalidBlock = Block.fromRaw({ + const invalidBlock = nodeTest.strategy.newBlock({ header: { ...block.header, transactionCommitment: transactionCommitment(reorderedTransactions), @@ -272,7 +272,7 @@ describe('Verifier', () => { }, ) - const invalidMinersBlock = Block.fromRaw({ + const invalidMinersBlock = nodeTest.strategy.newBlock({ header: { ...minersBlock.header, transactionCommitment: transactionCommitment([invalidMinersTransaction]), @@ -289,7 +289,7 @@ describe('Verifier', () => { it('rejects a block with no transactions', async () => { const block = await useMinerBlockFixture(nodeTest.node.chain) - const invalidBlock = Block.fromRaw({ + const invalidBlock = nodeTest.strategy.newBlock({ header: { ...block.header, transactionCommitment: transactionCommitment([]), @@ -316,7 +316,7 @@ describe('Verifier', () => { const invalidTransactions = [...block.transactions, extraTransaction] - const invalidBlock = Block.fromRaw({ + const invalidBlock = nodeTest.strategy.newBlock({ header: { ...block.header, transactionCommitment: transactionCommitment(invalidTransactions), @@ -524,9 +524,9 @@ describe('Verifier', () => { ...header, graffiti: Buffer.alloc(31), }, + header.hash, header.noteSize, header.work, - header.hash, ) expect(nodeTest.verifier.verifyBlockHeader(invalidHeader31Byte)).toMatchObject({ @@ -539,9 +539,9 @@ describe('Verifier', () => { ...header, graffiti: Buffer.alloc(33), }, + header.hash, header.noteSize, header.work, - header.hash, ) expect(nodeTest.verifier.verifyBlockHeader(invalidHeader33Byte)).toMatchObject({ @@ -639,7 +639,7 @@ describe('Verifier', () => { nodeTest.verifier.enableVerifyTarget = true const block = await useMinerBlockFixture(nodeTest.chain) - const invalidHeader = new BlockHeader({ + const invalidHeader = nodeTest.strategy.newBlockHeader({ ...block.header, target: Target.minTarget(), }) @@ -655,7 +655,7 @@ describe('Verifier', () => { it('Is invalid when the sequence is wrong', async () => { const block = await useMinerBlockFixture(nodeTest.chain) - const invalidHeader = new BlockHeader({ + const invalidHeader = nodeTest.strategy.newBlockHeader({ ...block.header, sequence: 9999, }) @@ -692,7 +692,7 @@ describe('Verifier', () => { }) it('fails validation when timestamp is too low', async () => { - const invalidHeader = new BlockHeader({ + const invalidHeader = nodeTest.strategy.newBlockHeader({ ...header, timestamp: new Date( prevHeader.timestamp.getTime() - @@ -713,7 +713,7 @@ describe('Verifier', () => { .spyOn(global.Date, 'now') .mockImplementationOnce(() => prevHeader.timestamp.getTime() + 40 * 1000) - const invalidHeader = new BlockHeader({ + const invalidHeader = nodeTest.strategy.newBlockHeader({ ...header, timestamp: new Date( prevHeader.timestamp.getTime() + @@ -734,7 +734,7 @@ describe('Verifier', () => { .spyOn(global.Date, 'now') .mockImplementationOnce(() => prevHeader.timestamp.getTime() + 1 * 1000) - const invalidHeader = new BlockHeader({ + const invalidHeader = nodeTest.strategy.newBlockHeader({ ...header, timestamp: new Date(prevHeader.timestamp.getTime() - 1 * 1000), }) @@ -751,7 +751,7 @@ describe('Verifier', () => { .spyOn(global.Date, 'now') .mockImplementationOnce(() => prevHeader.timestamp.getTime() + 1 * 1000) - const invalidHeader = new BlockHeader({ + const invalidHeader = nodeTest.strategy.newBlockHeader({ ...header, timestamp: new Date(prevHeader.timestamp.getTime() + 1 * 1000), }) @@ -792,7 +792,7 @@ describe('Verifier', () => { }) it('fails validation when timestamp is too low', async () => { - const invalidHeader = new BlockHeader({ + const invalidHeader = nodeTest.strategy.newBlockHeader({ ...header, timestamp: new Date( prevHeader.timestamp.getTime() - @@ -813,7 +813,7 @@ describe('Verifier', () => { .spyOn(global.Date, 'now') .mockImplementationOnce(() => prevHeader.timestamp.getTime() + 40 * 1000) - const invalidHeader = new BlockHeader({ + const invalidHeader = nodeTest.strategy.newBlockHeader({ ...header, timestamp: new Date( prevHeader.timestamp.getTime() + @@ -830,7 +830,7 @@ describe('Verifier', () => { }) it('fails validation when timestamp is smaller than previous block', async () => { - const invalidHeader = new BlockHeader({ + const invalidHeader = nodeTest.strategy.newBlockHeader({ ...header, timestamp: new Date(prevHeader.timestamp.getTime() - 1 * 1000), }) @@ -844,7 +844,7 @@ describe('Verifier', () => { }) it('fails validation when timestamp is same as previous block', async () => { - const invalidHeader = new BlockHeader({ + const invalidHeader = nodeTest.strategy.newBlockHeader({ ...header, timestamp: new Date(prevHeader.timestamp.getTime()), }) @@ -862,7 +862,7 @@ describe('Verifier', () => { .spyOn(global.Date, 'now') .mockImplementationOnce(() => prevHeader.timestamp.getTime() + 1 * 1000) - const invalidHeader = new BlockHeader({ + const invalidHeader = nodeTest.strategy.newBlockHeader({ ...header, timestamp: new Date(prevHeader.timestamp.getTime() + 1 * 1000), }) @@ -895,15 +895,17 @@ describe('Verifier', () => { const newBlock = await useMinerBlockFixture(chain) await expect(chain).toAddBlock(newBlock) - const invalidNewBlock = Block.fromRaw({ - header: { - ...newBlock.header, - noteCommitment: Buffer.alloc(newBlock.header.noteCommitment.length, 'NOOO'), + const invalidNewBlock = nodeTest.strategy.newBlock( + { + header: { + ...newBlock.header, + noteCommitment: Buffer.alloc(newBlock.header.noteCommitment.length, 'NOOO'), + }, + transactions: newBlock.transactions, }, - transactions: newBlock.transactions, - }) - invalidNewBlock.header.noteSize = newBlock.header.noteSize - invalidNewBlock.header.work = newBlock.header.work + newBlock.header.noteSize, + newBlock.header.work, + ) await expect( nodeTest.verifier.verifyConnectedBlock(invalidNewBlock), diff --git a/ironfish/src/genesis/addGenesisTransaction.ts b/ironfish/src/genesis/addGenesisTransaction.ts index 2f2fdcfd48..1f70b6f939 100644 --- a/ironfish/src/genesis/addGenesisTransaction.ts +++ b/ironfish/src/genesis/addGenesisTransaction.ts @@ -9,7 +9,7 @@ import { } from '@ironfish/rust-nodejs' import { Logger } from '../logger' import { FullNode } from '../node' -import { Block, BlockHeader } from '../primitives' +import { Block } from '../primitives' import { transactionCommitment } from '../primitives/blockheader' import { Transaction, TransactionVersion } from '../primitives/transaction' import { CurrencyUtils } from '../utils' @@ -139,7 +139,7 @@ export async function addGenesisTransaction( graffiti: genesisBlock.header.graffiti, } - const newGenesisHeader = new BlockHeader(rawHeader, noteSize) + const newGenesisHeader = node.chain.strategy.newBlockHeader(rawHeader, noteSize) genesisBlock.header = newGenesisHeader diff --git a/ironfish/src/genesis/genesis.test.slow.ts b/ironfish/src/genesis/genesis.test.slow.ts index da2788a14d..1b20bb7063 100644 --- a/ironfish/src/genesis/genesis.test.slow.ts +++ b/ironfish/src/genesis/genesis.test.slow.ts @@ -121,7 +121,7 @@ describe('Create genesis block', () => { // Deserialize the block and add it to the new chain const result = IJSON.parse(jsonedBlock) as SerializedBlock - const deserializedBlock = BlockSerde.deserialize(result) + const deserializedBlock = BlockSerde.deserialize(result, nodeTest.strategy) const addedBlock = await newChain.addBlock(deserializedBlock) expect(addedBlock.isAdded).toBe(true) @@ -274,7 +274,7 @@ describe('addGenesisTransaction', () => { // Deserialize the block and add it to the new chain const result = IJSON.parse(jsonedBlock) as SerializedBlock - const deserializedBlock = BlockSerde.deserialize(result) + const deserializedBlock = BlockSerde.deserialize(result, nodeTest.strategy) const addedBlock = await chain.addBlock(deserializedBlock) expect(addedBlock.isAdded).toBe(true) diff --git a/ironfish/src/genesis/makeGenesisBlock.ts b/ironfish/src/genesis/makeGenesisBlock.ts index a2e8a10c99..cc1a954ff6 100644 --- a/ironfish/src/genesis/makeGenesisBlock.ts +++ b/ironfish/src/genesis/makeGenesisBlock.ts @@ -170,17 +170,18 @@ export async function makeGenesisBlock( GraffitiUtils.fromString('genesis'), ) - const genesisBlock = Block.fromRaw({ - header: { - ...block.header, - target: info.target, - timestamp: new Date(info.timestamp), + const genesisBlock = chain.strategy.newBlock( + { + header: { + ...block.header, + target: info.target, + timestamp: new Date(info.timestamp), + }, + transactions: block.transactions, }, - transactions: block.transactions, - }) - - genesisBlock.header.noteSize = block.header.noteSize - genesisBlock.header.work = block.header.work + block.header.noteSize, + block.header.work, + ) logger.info('Block complete.') return { block: genesisBlock } diff --git a/ironfish/src/mining/manager.test.slow.ts b/ironfish/src/mining/manager.test.slow.ts index ca61aac72d..9f0ec2aad1 100644 --- a/ironfish/src/mining/manager.test.slow.ts +++ b/ironfish/src/mining/manager.test.slow.ts @@ -4,7 +4,7 @@ import { Assert } from '../assert' import { VerificationResultReason } from '../consensus' import { getBlockWithMinersFeeSize, getTransactionSize } from '../network/utils/serializers' -import { Block, Target, Transaction } from '../primitives' +import { Target, Transaction } from '../primitives' import { TransactionVersion } from '../primitives/transaction' import { BlockTemplateSerde, SerializedBlockTemplate } from '../serde' import { @@ -626,7 +626,7 @@ describe('Mining manager', () => { // Create 2 blocks at the same sequence, one with higher difficulty const blockA1 = await useMinerBlockFixture(chain, undefined, account, wallet) const blockB1Temp = await useMinerBlockFixture(chain, undefined, account, wallet) - const blockB1 = Block.fromRaw({ + const blockB1 = nodeTest.strategy.newBlock({ header: { ...blockB1Temp.header, target: Target.fromDifficulty(blockA1.header.target.toDifficulty() + 1n), @@ -641,8 +641,8 @@ describe('Mining manager', () => { await expect(chain).toAddBlock(blockB1) // Increase difficulty of submitted template so it - const blockA2Temp = BlockTemplateSerde.deserialize(templateA2) - const blockA2 = Block.fromRaw({ + const blockA2Temp = BlockTemplateSerde.deserialize(templateA2, nodeTest.strategy) + const blockA2 = nodeTest.strategy.newBlock({ header: { ...blockA2Temp.header, target: Target.fromDifficulty(blockA2Temp.header.target.toDifficulty() + 2n), @@ -680,7 +680,7 @@ describe('Mining manager', () => { MINED_RESULT.SUCCESS, ) - const submittedBlock = BlockTemplateSerde.deserialize(template) + const submittedBlock = BlockTemplateSerde.deserialize(template, nodeTest.strategy) const newBlock = onNewBlockSpy.mock.calls[0][0] expect(newBlock.header.hash).toEqual(submittedBlock.header.hash) }) diff --git a/ironfish/src/mining/manager.ts b/ironfish/src/mining/manager.ts index e0537593cd..3e9b9ef3cd 100644 --- a/ironfish/src/mining/manager.ts +++ b/ironfish/src/mining/manager.ts @@ -198,7 +198,7 @@ export class MiningManager { this.metrics.mining_newBlockTemplate.add(BenchUtils.end(connectedAt)) this.streamBlockTemplate(currentBlock, template) - const block = BlockTemplateSerde.deserialize(template) + const block = BlockTemplateSerde.deserialize(template, this.chain.strategy) const verification = await this.chain.verifier.verifyBlock(block, { verifyTarget: false, }) @@ -366,7 +366,7 @@ export class MiningManager { } async submitBlockTemplate(blockTemplate: SerializedBlockTemplate): Promise { - const block = BlockTemplateSerde.deserialize(blockTemplate) + const block = BlockTemplateSerde.deserialize(blockTemplate, this.chain.strategy) const blockDisplay = `${block.header.hash.toString('hex')} (${block.header.sequence})` if (!block.header.previousBlockHash.equals(this.node.chain.head.hash)) { diff --git a/ironfish/src/network/messages/getBlocks.test.ts b/ironfish/src/network/messages/getBlocks.test.ts index fc918b7646..4aaa8f7898 100644 --- a/ironfish/src/network/messages/getBlocks.test.ts +++ b/ironfish/src/network/messages/getBlocks.test.ts @@ -1,7 +1,7 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { Block, BlockHeader, Target } from '../../primitives' +import { Block, Target } from '../../primitives' import { transactionCommitment } from '../../primitives/blockheader' import { createNodeTest, @@ -36,7 +36,7 @@ describe('GetBlocksResponse', () => { const message = new GetBlocksResponse( [ new Block( - new BlockHeader({ + nodeTest.strategy.newBlockHeader({ sequence: 2, previousBlockHash: Buffer.alloc(32, 2), noteCommitment: Buffer.alloc(32, 4), @@ -49,7 +49,7 @@ describe('GetBlocksResponse', () => { [transactionA, transactionB], ), new Block( - new BlockHeader({ + nodeTest.strategy.newBlockHeader({ sequence: 2, previousBlockHash: Buffer.alloc(32, 1), noteCommitment: Buffer.alloc(32, 5), diff --git a/ironfish/src/network/messages/getCompactBlock.test.ts b/ironfish/src/network/messages/getCompactBlock.test.ts index decdb7c3cf..1ab9c0de68 100644 --- a/ironfish/src/network/messages/getCompactBlock.test.ts +++ b/ironfish/src/network/messages/getCompactBlock.test.ts @@ -1,7 +1,7 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { BlockHeader, Target } from '../../primitives' +import { Target } from '../../primitives' import { CompactBlock } from '../../primitives/block' import { createNodeTest, @@ -33,7 +33,7 @@ describe('GetCompactBlockResponse', () => { const transactionB = await useMinersTxFixture(nodeTest.node, account) const compactBlock: CompactBlock = { - header: new BlockHeader({ + header: nodeTest.strategy.newBlockHeader({ sequence: 2, previousBlockHash: Buffer.alloc(32, 2), noteCommitment: Buffer.alloc(32, 1), diff --git a/ironfish/src/network/messages/newCompactBlock.test.ts b/ironfish/src/network/messages/newCompactBlock.test.ts index d5217e08a9..464224073a 100644 --- a/ironfish/src/network/messages/newCompactBlock.test.ts +++ b/ironfish/src/network/messages/newCompactBlock.test.ts @@ -1,7 +1,7 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { BlockHeader, Target } from '../../primitives' +import { Target } from '../../primitives' import { CompactBlock } from '../../primitives/block' import { transactionCommitment } from '../../primitives/blockheader' import { @@ -40,7 +40,7 @@ describe('NewCompactBlockMessage', () => { const transactionB = await useMinersTxFixture(nodeTest.node, account) const compactBlock: CompactBlock = { - header: new BlockHeader({ + header: { sequence: 2, previousBlockHash: Buffer.alloc(32, 2), noteCommitment: Buffer.alloc(32, 1), @@ -49,7 +49,7 @@ describe('NewCompactBlockMessage', () => { randomness: BigInt(1), timestamp: new Date(200000), graffiti: Buffer.alloc(32, 'graffiti1', 'utf8'), - }), + }, transactions: [ { transaction: transactionA, index: 0 }, { transaction: transactionB, index: 2 }, diff --git a/ironfish/src/network/peerNetwork.test.ts b/ironfish/src/network/peerNetwork.test.ts index c50f2eb446..d3592d661c 100644 --- a/ironfish/src/network/peerNetwork.test.ts +++ b/ironfish/src/network/peerNetwork.test.ts @@ -11,7 +11,7 @@ import net from 'net' import ws from 'ws' import { Assert } from '../assert' import { VerificationResultReason } from '../consensus/verifier' -import { Block, BlockHeader, Transaction } from '../primitives' +import { Block, Transaction } from '../primitives' import { CompactBlock } from '../primitives/block' import { useAccountFixture, @@ -763,7 +763,7 @@ describe('PeerNetwork', () => { expect(sendSpy).not.toHaveBeenCalled() } - const invalidHeader = new BlockHeader(invalidBlock.header) + const invalidHeader = nodeTest.strategy.newBlockHeader(invalidBlock.header) await expect(chain.hasBlock(invalidHeader.hash)).resolves.toBe(false) expect(chain.isInvalid(invalidHeader)).toBe(reason) } diff --git a/ironfish/src/network/peerNetwork.ts b/ironfish/src/network/peerNetwork.ts index 5d4efd0108..dea1ccd96d 100644 --- a/ironfish/src/network/peerNetwork.ts +++ b/ironfish/src/network/peerNetwork.ts @@ -614,7 +614,9 @@ export class PeerNetwork { throw new Error(`Invalid GetBlockHeadersResponse: ${message.displayType()}`) } - const headers = response.headers.map((rawHeader) => new BlockHeader(rawHeader)) + const headers = response.headers.map((rawHeader) => + this.chain.strategy.newBlockHeader(rawHeader), + ) return { headers, time: BenchUtils.end(begin) } } @@ -637,7 +639,7 @@ export class PeerNetwork { const exceededSoftLimit = response.getSize() >= SOFT_MAX_MESSAGE_SIZE const isMessageFull = exceededSoftLimit || response.blocks.length >= limit - const blocks = response.blocks.map((rawBlock) => Block.fromRaw(rawBlock)) + const blocks = response.blocks.map((rawBlock) => this.chain.strategy.newBlock(rawBlock)) return { blocks, time: BenchUtils.end(begin), isMessageFull } } @@ -787,7 +789,7 @@ export class PeerNetwork { return } - const header = new BlockHeader(compactBlock.header) + const header = this.chain.strategy.newBlockHeader(compactBlock.header) // mark the block as received in the block fetcher and decide whether to continue // to validate this compact block or not @@ -1173,7 +1175,7 @@ export class PeerNetwork { return } - const block = Block.fromRaw(rawBlock) + const block = this.chain.strategy.newBlock(rawBlock) if (await this.alreadyHaveBlock(block.header)) { return diff --git a/ironfish/src/primitives/block.test.ts b/ironfish/src/primitives/block.test.ts index 25ef956471..08b3c82316 100644 --- a/ironfish/src/primitives/block.test.ts +++ b/ironfish/src/primitives/block.test.ts @@ -8,7 +8,7 @@ import { useMinersTxFixture, } from '../testUtilities/fixtures' import { createNodeTest } from '../testUtilities/nodeTest' -import { Block, BlockSerde, SerializedBlock } from './block' +import { BlockSerde, SerializedBlock } from './block' describe('Block', () => { const nodeTest = createNodeTest() @@ -36,14 +36,14 @@ describe('Block', () => { const serialized = BlockSerde.serialize(block) expect(serialized).toMatchObject({ header: { timestamp: expect.any(Number) } }) - const deserialized = BlockSerde.deserialize(serialized) + const deserialized = BlockSerde.deserialize(serialized, nodeTest.strategy) expect(block.equals(deserialized)).toBe(true) }) it('throws when deserializing invalid block', () => { - expect(() => BlockSerde.deserialize({ bad: 'data' } as unknown as SerializedBlock)).toThrow( - 'Unable to deserialize', - ) + expect(() => + BlockSerde.deserialize({ bad: 'data' } as unknown as SerializedBlock, nodeTest.strategy), + ).toThrow('Unable to deserialize') }) it('check block equality', async () => { @@ -52,10 +52,10 @@ describe('Block', () => { const { block: block1 } = await useBlockWithTx(nodeTest.node, account, account) // Header change - const block2 = BlockSerde.deserialize(BlockSerde.serialize(block1)) + const block2 = BlockSerde.deserialize(BlockSerde.serialize(block1), nodeTest.strategy) expect(block1.equals(block2)).toBe(true) - let toCompare = Block.fromRaw({ + let toCompare = nodeTest.strategy.newBlock({ header: { ...block2.header, randomness: BigInt(400), @@ -64,7 +64,7 @@ describe('Block', () => { }) expect(block1.equals(toCompare)).toBe(false) - toCompare = Block.fromRaw({ + toCompare = nodeTest.strategy.newBlock({ header: { ...block2.header, sequence: block2.header.sequence + 1, @@ -73,7 +73,7 @@ describe('Block', () => { }) expect(block1.equals(toCompare)).toBe(false) - toCompare = Block.fromRaw({ + toCompare = nodeTest.strategy.newBlock({ header: { ...block2.header, timestamp: new Date(block2.header.timestamp.valueOf() + 1), @@ -83,13 +83,13 @@ describe('Block', () => { expect(block1.equals(toCompare)).toBe(false) // Transactions length - const block3 = BlockSerde.deserialize(BlockSerde.serialize(block1)) + const block3 = BlockSerde.deserialize(BlockSerde.serialize(block1), nodeTest.strategy) expect(block1.equals(block3)).toBe(true) block3.transactions.pop() expect(block1.equals(block3)).toBe(false) // Transaction equality - const block4 = BlockSerde.deserialize(BlockSerde.serialize(block1)) + const block4 = BlockSerde.deserialize(BlockSerde.serialize(block1), nodeTest.strategy) expect(block1.equals(block4)).toBe(true) block4.transactions.pop() block4.transactions.push(tx) diff --git a/ironfish/src/primitives/block.ts b/ironfish/src/primitives/block.ts index fc6b1d472a..e8f4bb34e7 100644 --- a/ironfish/src/primitives/block.ts +++ b/ironfish/src/primitives/block.ts @@ -4,6 +4,7 @@ import { zip } from 'lodash' import { Assert } from '../assert' +import { Strategy } from '../strategy' import { BlockHeader, BlockHeaderSerde, @@ -140,11 +141,6 @@ export class Block { ], } } - - static fromRaw(raw: RawBlock): Block { - const header = new BlockHeader(raw.header) - return new Block(header, raw.transactions) - } } export type CompactBlockTransaction = { @@ -178,7 +174,7 @@ export class BlockSerde { } } - static deserialize(data: SerializedBlock): Block { + static deserialize(data: SerializedBlock, strategy: Strategy): Block { if ( typeof data === 'object' && data !== null && @@ -186,7 +182,7 @@ export class BlockSerde { 'transactions' in data && Array.isArray(data.transactions) ) { - const header = BlockHeaderSerde.deserialize(data.header) + const header = BlockHeaderSerde.deserialize(data.header, strategy) const transactions = data.transactions.map((t) => new Transaction(t)) return new Block(header, transactions) } diff --git a/ironfish/src/primitives/blockheader.test.ts b/ironfish/src/primitives/blockheader.test.ts index 1bd152a882..145c7a0101 100644 --- a/ironfish/src/primitives/blockheader.test.ts +++ b/ironfish/src/primitives/blockheader.test.ts @@ -4,9 +4,9 @@ import { blake3 } from '@napi-rs/blake-hash' import { v4 as uuid } from 'uuid' +import { createNodeTest } from '../testUtilities' import { GraffitiUtils } from '../utils' import { - BlockHeader, BlockHeaderSerde, isBlockHeavier, isBlockLater, @@ -92,8 +92,10 @@ describe('transactionMerkleRoot', () => { }) describe('BlockHeader', () => { + const nodeTest = createNodeTest() + it('checks equal block headers', () => { - const header1 = new BlockHeader({ + const header1 = nodeTest.strategy.newBlockHeader({ sequence: 5, previousBlockHash: Buffer.alloc(32), noteCommitment: Buffer.alloc(32, 'header'), @@ -104,41 +106,55 @@ describe('BlockHeader', () => { graffiti: Buffer.alloc(32), }) - expect(header1.equals(new BlockHeader({ ...header1 }))).toBe(true) + expect(header1.equals(nodeTest.strategy.newBlockHeader({ ...header1 }))).toBe(true) // sequence - expect(header1.equals(new BlockHeader({ ...header1, sequence: 6 }))).toBe(false) + expect(header1.equals(nodeTest.strategy.newBlockHeader({ ...header1, sequence: 6 }))).toBe( + false, + ) // note commitment expect( header1.equals( - new BlockHeader({ ...header1, noteCommitment: Buffer.alloc(32, 'not header') }), + nodeTest.strategy.newBlockHeader({ + ...header1, + noteCommitment: Buffer.alloc(32, 'not header'), + }), ), ).toBe(false) // target - expect(header1.equals(new BlockHeader({ ...header1, target: new Target(10) }))).toBe(false) + expect( + header1.equals(nodeTest.strategy.newBlockHeader({ ...header1, target: new Target(10) })), + ).toBe(false) // randomness - expect(header1.equals(new BlockHeader({ ...header1, randomness: BigInt(19) }))).toBe(false) + expect( + header1.equals(nodeTest.strategy.newBlockHeader({ ...header1, randomness: BigInt(19) })), + ).toBe(false) // timestamp - expect(header1.equals(new BlockHeader({ ...header1, timestamp: new Date(1000) }))).toBe( - false, - ) + expect( + header1.equals( + nodeTest.strategy.newBlockHeader({ ...header1, timestamp: new Date(1000) }), + ), + ).toBe(false) // graffiti expect( - header1.equals(new BlockHeader({ ...header1, graffiti: Buffer.alloc(32, 'a') })), + header1.equals( + nodeTest.strategy.newBlockHeader({ ...header1, graffiti: Buffer.alloc(32, 'a') }), + ), ).toBe(false) }) }) describe('BlockHeaderSerde', () => { const serde = BlockHeaderSerde + const nodeTest = createNodeTest() it('serializes and deserializes a block header', () => { - const header = new BlockHeader({ + const header = nodeTest.strategy.newBlockHeader({ sequence: 5, previousBlockHash: Buffer.alloc(32), noteCommitment: Buffer.alloc(32), @@ -150,12 +166,12 @@ describe('BlockHeaderSerde', () => { }) const serialized = serde.serialize(header) - const deserialized = serde.deserialize(serialized) + const deserialized = serde.deserialize(serialized, nodeTest.strategy) expect(header.equals(deserialized)).toBe(true) }) it('checks block is later than', () => { - const header1 = new BlockHeader({ + const header1 = nodeTest.strategy.newBlockHeader({ sequence: 5, previousBlockHash: Buffer.alloc(32), noteCommitment: Buffer.alloc(32), @@ -166,20 +182,26 @@ describe('BlockHeaderSerde', () => { graffiti: Buffer.alloc(32), }) - expect(isBlockLater(header1, new BlockHeader({ ...header1 }))).toBe(false) + expect(isBlockLater(header1, nodeTest.strategy.newBlockHeader({ ...header1 }))).toBe(false) expect( - isBlockLater(header1, new BlockHeader({ ...header1, sequence: header1.sequence - 1 })), + isBlockLater( + header1, + nodeTest.strategy.newBlockHeader({ ...header1, sequence: header1.sequence - 1 }), + ), ).toBe(true) - const header2 = new BlockHeader({ ...header1, graffiti: Buffer.alloc(32, 'a') }) + const header2 = nodeTest.strategy.newBlockHeader({ + ...header1, + graffiti: Buffer.alloc(32, 'a'), + }) const header1HashIsGreater = header1.hash.compare(header2.hash) < 0 expect(isBlockLater(header1, header2)).toBe(header1HashIsGreater) }) it('checks block is heavier than', () => { - const header1 = new BlockHeader({ + const header1 = nodeTest.strategy.newBlockHeader({ sequence: 5, previousBlockHash: Buffer.alloc(32), noteCommitment: Buffer.alloc(32), @@ -191,28 +213,28 @@ describe('BlockHeaderSerde', () => { }) const serialized = serde.serialize(header1) - let header2 = serde.deserialize(serialized) + let header2 = serde.deserialize(serialized, nodeTest.strategy) expect(isBlockHeavier(header1, header2)).toBe(false) header1.work = BigInt(1) header2.work = BigInt(0) expect(isBlockHeavier(header1, header2)).toBe(true) - header2 = new BlockHeader({ ...header1, sequence: header1.sequence - 1 }) + header2 = nodeTest.strategy.newBlockHeader({ ...header1, sequence: header1.sequence - 1 }) header1.work = BigInt(0) header2.work = BigInt(0) expect(isBlockHeavier(header1, header2)).toBe(true) - header2 = new BlockHeader({ ...header1, target: new Target(200) }) + header2 = nodeTest.strategy.newBlockHeader({ ...header1, target: new Target(200) }) header1.work = BigInt(0) header2.work = BigInt(0) expect(isBlockHeavier(header1, header2)).toBe(true) - header2 = new BlockHeader({ ...header1, target: new Target(200) }) + header2 = nodeTest.strategy.newBlockHeader({ ...header1, target: new Target(200) }) header1.work = BigInt(0) header2.work = BigInt(0) - header2 = new BlockHeader({ ...header1, graffiti: Buffer.alloc(32, 'a') }) + header2 = nodeTest.strategy.newBlockHeader({ ...header1, graffiti: Buffer.alloc(32, 'a') }) const header1HashIsGreater = header1.hash.compare(header2.hash) < 0 expect(isBlockHeavier(header1, header2)).toBe(header1HashIsGreater) }) diff --git a/ironfish/src/primitives/blockheader.ts b/ironfish/src/primitives/blockheader.ts index a799f6bfee..178201ee75 100644 --- a/ironfish/src/primitives/blockheader.ts +++ b/ironfish/src/primitives/blockheader.ts @@ -6,6 +6,7 @@ import { blake3 } from '@napi-rs/blake-hash' import bufio from 'bufio' import { Assert } from '../assert' import { BlockHashSerdeInstance, GraffitiSerdeInstance } from '../serde' +import { Strategy } from '../strategy' import { BigIntUtils } from '../utils/bigint' import { NoteEncryptedHash, SerializedNoteEncryptedHash } from './noteEncrypted' import { Target } from './target' @@ -167,7 +168,7 @@ export class BlockHeader { public readonly hash: Buffer - constructor(raw: RawBlockHeader, noteSize?: number | null, work = BigInt(0), hash?: Buffer) { + constructor(raw: RawBlockHeader, hash: Buffer, noteSize?: number | null, work = BigInt(0)) { this.sequence = raw.sequence this.previousBlockHash = raw.previousBlockHash this.noteCommitment = raw.noteCommitment @@ -178,17 +179,7 @@ export class BlockHeader { this.graffiti = raw.graffiti this.noteSize = noteSize ?? null this.work = work - this.hash = hash || this.computeHash() - } - - /** - * Hash all the values in the block header to get a commitment to the entire - * header and the global trees it models. - */ - computeHash(): BlockHash { - const header = this.serialize() - - return hashBlockHeader(header) + this.hash = hash } /** @@ -282,8 +273,8 @@ export class BlockHeaderSerde { } } - static deserialize(data: SerializedBlockHeader): BlockHeader { - return new BlockHeader( + static deserialize(data: SerializedBlockHeader, strategy: Strategy): BlockHeader { + return strategy.newBlockHeader( { sequence: Number(data.sequence), previousBlockHash: Buffer.from( diff --git a/ironfish/src/serde/BlockTemplateSerde.ts b/ironfish/src/serde/BlockTemplateSerde.ts index 08df77328c..bd1b754eea 100644 --- a/ironfish/src/serde/BlockTemplateSerde.ts +++ b/ironfish/src/serde/BlockTemplateSerde.ts @@ -3,10 +3,10 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Block } from '../primitives/block' -import { BlockHeader } from '../primitives/blockheader' import { NoteEncryptedHashSerde } from '../primitives/noteEncrypted' import { Target } from '../primitives/target' import { Transaction } from '../primitives/transaction' +import { Strategy } from '../strategy' import { BigIntUtils } from '../utils' export type SerializedBlockTemplate = { @@ -54,10 +54,10 @@ export class BlockTemplateSerde { } } - static deserialize(blockTemplate: SerializedBlockTemplate): Block { + static deserialize(blockTemplate: SerializedBlockTemplate, strategy: Strategy): Block { const noteHasher = new NoteEncryptedHashSerde() - const header = new BlockHeader({ + const header = strategy.newBlockHeader({ sequence: blockTemplate.header.sequence, previousBlockHash: Buffer.from(blockTemplate.header.previousBlockHash, 'hex'), noteCommitment: noteHasher.deserialize( diff --git a/ironfish/src/strategy.ts b/ironfish/src/strategy.ts index ba1d250987..95bb2eabca 100644 --- a/ironfish/src/strategy.ts +++ b/ironfish/src/strategy.ts @@ -2,7 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { blake3 } from '@napi-rs/blake-hash' +import bufio from 'bufio' import { Consensus } from './consensus' +import { Block, RawBlock } from './primitives/block' +import { BlockHash, BlockHeader, RawBlockHeader } from './primitives/blockheader' import { Transaction } from './primitives/transaction' import { MathUtils } from './utils' import { WorkerPool } from './workerPool' @@ -92,4 +96,33 @@ export class Strategy { return this.workerPool.createMinersFee(minerSpendKey, amount, '', transactionVersion) } + + hashHeader(header: RawBlockHeader): BlockHash { + const serialized = serializeHeaderBlake3(header) + return blake3(serialized) + } + + newBlockHeader(raw: RawBlockHeader, noteSize?: number | null, work?: bigint): BlockHeader { + const hash = this.hashHeader(raw) + return new BlockHeader(raw, hash, noteSize, work) + } + + newBlock(raw: RawBlock, noteSize?: number | null, work?: bigint): Block { + const header = this.newBlockHeader(raw.header, noteSize, work) + return new Block(header, raw.transactions) + } +} + +function serializeHeaderBlake3(header: RawBlockHeader): Buffer { + const bw = bufio.write(180) + bw.writeBigU64BE(header.randomness) + bw.writeU32(header.sequence) + bw.writeHash(header.previousBlockHash) + bw.writeHash(header.noteCommitment) + bw.writeHash(header.transactionCommitment) + bw.writeBigU256BE(header.target.asBigInt()) + bw.writeU64(header.timestamp.getTime()) + bw.writeBytes(header.graffiti) + + return bw.render() } diff --git a/ironfish/src/testUtilities/fixtures/blocks.ts b/ironfish/src/testUtilities/fixtures/blocks.ts index 6788be1b10..1e8de7e92f 100644 --- a/ironfish/src/testUtilities/fixtures/blocks.ts +++ b/ironfish/src/testUtilities/fixtures/blocks.ts @@ -57,7 +57,7 @@ export async function useBlockFixture( return BlockSerde.serialize(block) }, deserialize: (serialized: SerializedBlock): Block => { - return BlockSerde.deserialize(serialized) + return BlockSerde.deserialize(serialized, chain.strategy) }, }) } diff --git a/ironfish/src/utils/blockutil.test.ts b/ironfish/src/utils/blockutil.test.ts index 64cfbc58a9..c491b90056 100644 --- a/ironfish/src/utils/blockutil.test.ts +++ b/ironfish/src/utils/blockutil.test.ts @@ -1,7 +1,6 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { BlockHeader } from '../primitives' import { useMinerBlockFixture } from '../testUtilities' import { createNodeTest } from '../testUtilities/nodeTest' import { BlockchainUtils, getBlockRange } from './blockchain' @@ -69,7 +68,7 @@ describe('BlockchainUtils', () => { [{ start: 3.14, stop: 6.28 }, 3, 6], [{ start: 6.28, stop: 3.14 }, 6, 6], ])('%o returns %d %d', (param, expectedStart, expectedStop) => { - nodeTest.chain.latest = new BlockHeader({ + nodeTest.chain.latest = nodeTest.strategy.newBlockHeader({ ...nodeTest.chain.latest, sequence: 10000, }) @@ -80,7 +79,7 @@ describe('BlockchainUtils', () => { }) it('{ start: null, stop: 6000 } returns 1 6000', () => { - nodeTest.chain.latest = new BlockHeader({ + nodeTest.chain.latest = nodeTest.strategy.newBlockHeader({ ...nodeTest.chain.latest, sequence: 10000, }) @@ -91,7 +90,7 @@ describe('BlockchainUtils', () => { }) it('{ start: 6000, stop: null } returns 6000 10000', () => { - nodeTest.chain.latest = new BlockHeader({ + nodeTest.chain.latest = nodeTest.strategy.newBlockHeader({ ...nodeTest.chain.latest, sequence: 10000, })