From 6980076213580a361783f0f99a03e0ccafd70b96 Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Mon, 29 Jan 2024 11:06:44 -0800 Subject: [PATCH 01/27] Use chain.head in feeEstimator instead of chain.latest (#4621) --- ironfish/src/memPool/feeEstimator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ironfish/src/memPool/feeEstimator.ts b/ironfish/src/memPool/feeEstimator.ts index 2411e5e74a..a60c625e8e 100644 --- a/ironfish/src/memPool/feeEstimator.ts +++ b/ironfish/src/memPool/feeEstimator.ts @@ -64,7 +64,7 @@ export class FeeEstimator { return } - let currentBlockHash = chain.latest.hash + let currentBlockHash = chain.head.hash for (let i = 0; i < this.maxBlockHistory; i++) { const currentBlock = await chain.getBlock(currentBlockHash) From 907f82ca1f251df5a28e87b012fb4b6800371cc1 Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Mon, 29 Jan 2024 11:58:43 -0800 Subject: [PATCH 02/27] Only deserialize genesis seed block once in chain (#4617) --- ironfish/src/blockchain/blockchain.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/ironfish/src/blockchain/blockchain.ts b/ironfish/src/blockchain/blockchain.ts index e3253de915..5b85380678 100644 --- a/ironfish/src/blockchain/blockchain.ts +++ b/ironfish/src/blockchain/blockchain.ts @@ -28,7 +28,6 @@ import { import { BlockHash, BlockHeader, - BlockHeaderSerde, isBlockHeavier, isBlockLater, transactionCommitment, @@ -199,9 +198,7 @@ export class Blockchain { return Math.max(Math.min(1, progress), 0) } - private async seed() { - const genesis = BlockSerde.deserialize(this.seedGenesisBlock, this.strategy) - + private async seed(genesis: Block): Promise { const result = await this.addBlock(genesis) Assert.isTrue(result.isAdded, `Could not seed genesis: ${result.reason || 'unknown'}`) Assert.isEqual(result.isFork, false) @@ -227,17 +224,17 @@ export class Blockchain { private async load(): Promise { let genesisHeader = await this.getHeaderAtSequence(GENESIS_BLOCK_SEQUENCE) + const seedGenesisBlock = BlockSerde.deserialize(this.seedGenesisBlock, this.strategy) + if (genesisHeader) { Assert.isTrue( - genesisHeader.hash.equals( - BlockHeaderSerde.deserialize(this.seedGenesisBlock.header, this.strategy).hash, - ), + genesisHeader.hash.equals(seedGenesisBlock.header.hash), 'Genesis block in network definition does not match existing chain genesis block', ) } if (!genesisHeader && this.autoSeed) { - genesisHeader = await this.seed() + genesisHeader = await this.seed(seedGenesisBlock) } if (genesisHeader) { From 0f48823d29b7efb1bbae2b2106e37236c9813249 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Mon, 29 Jan 2024 13:13:48 -0800 Subject: [PATCH 03/27] Remove TestnetConsensus (#4628) This was just an alias for another type. --- ironfish/src/blockHasher.test.ts | 4 ++-- ironfish/src/consensus/consensus.ts | 6 ------ ironfish/src/node.ts | 4 ++-- ironfish/src/strategy.test.slow.ts | 8 ++++---- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/ironfish/src/blockHasher.test.ts b/ironfish/src/blockHasher.test.ts index 523ef6e06a..4d30eca781 100644 --- a/ironfish/src/blockHasher.test.ts +++ b/ironfish/src/blockHasher.test.ts @@ -4,7 +4,7 @@ import { blake3 } from '@napi-rs/blake-hash' import { BlockHasher, serializeHeaderBlake3, serializeHeaderFishHash } from './blockHasher' -import { TestnetConsensus } from './consensus' +import { Consensus } from './consensus' import { Target } from './primitives' import { RawBlockHeader } from './primitives/blockheader' import { FISH_HASH_CONTEXT } from './testUtilities' @@ -25,7 +25,7 @@ describe('Hashes blocks with correct hashing algorithm', () => { let blockHasher: BlockHasher beforeAll(() => { - const modifiedConsensus = new TestnetConsensus(consensusParameters) + const modifiedConsensus = new Consensus(consensusParameters) blockHasher = new BlockHasher({ consensus: modifiedConsensus, context: FISH_HASH_CONTEXT, diff --git a/ironfish/src/consensus/consensus.ts b/ironfish/src/consensus/consensus.ts index bb12787426..35193288e4 100644 --- a/ironfish/src/consensus/consensus.ts +++ b/ironfish/src/consensus/consensus.ts @@ -89,9 +89,3 @@ export class Consensus { } } } - -export class TestnetConsensus extends Consensus { - constructor(parameters: ConsensusParameters) { - super(parameters) - } -} diff --git a/ironfish/src/node.ts b/ironfish/src/node.ts index 6ab43c819e..03c0922a8c 100644 --- a/ironfish/src/node.ts +++ b/ironfish/src/node.ts @@ -5,7 +5,7 @@ import { BoxKeyPair, FishHashContext } from '@ironfish/rust-nodejs' import { v4 as uuid } from 'uuid' import { AssetsVerifier } from './assets' import { Blockchain } from './blockchain' -import { TestnetConsensus } from './consensus' +import { Consensus } from './consensus' import { Config, DEFAULT_DATA_DIR, @@ -268,7 +268,7 @@ export class FullNode { internal.set('networkIdentity', privateIdentity.secretKey.toString('hex')) await internal.save() - const consensus = new TestnetConsensus(networkDefinition.consensus) + const consensus = new Consensus(networkDefinition.consensus) if (consensus.isNeverActive('enableFishHash')) { fishHashContext = undefined diff --git a/ironfish/src/strategy.test.slow.ts b/ironfish/src/strategy.test.slow.ts index c3039466e2..8ef1251796 100644 --- a/ironfish/src/strategy.test.slow.ts +++ b/ironfish/src/strategy.test.slow.ts @@ -13,7 +13,7 @@ import { } from '@ironfish/rust-nodejs' import { blake3 } from '@napi-rs/blake-hash' import { serializeHeaderBlake3, serializeHeaderFishHash } from './blockHasher' -import { ConsensusParameters, TestnetConsensus } from './consensus' +import { Consensus, ConsensusParameters } from './consensus' import { MerkleTree } from './merkletree' import { LeafEncoding } from './merkletree/database/leaves' import { NodeEncoding } from './merkletree/database/nodes' @@ -104,7 +104,7 @@ describe('Demonstrate the Sapling API', () => { workerPool = new WorkerPool() strategy = new Strategy({ workerPool, - consensus: new TestnetConsensus(consensusParameters), + consensus: new Consensus(consensusParameters), fishHashContext: FISH_HASH_CONTEXT, }) }) @@ -259,7 +259,7 @@ describe('Demonstrate the Sapling API', () => { const key = generateKey() const modifiedStrategy = new Strategy({ workerPool, - consensus: new TestnetConsensus(modifiedParams), + consensus: new Consensus(modifiedParams), fishHashContext: FISH_HASH_CONTEXT, }) @@ -359,7 +359,7 @@ describe('Demonstrate the Sapling API', () => { beforeAll(() => { modifiedStrategy = new Strategy({ workerPool, - consensus: new TestnetConsensus(modifiedParams), + consensus: new Consensus(modifiedParams), fishHashContext: FISH_HASH_CONTEXT, }) }) From ad8f2e5dd41a990a4c9794b9340912fd4cc48b88 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 29 Jan 2024 13:17:15 -0800 Subject: [PATCH 04/27] derives public_address from view_key in build (#4624) * derives public_address from view_key in build UnsignedTransaction.build currently takes both a ViewKey _and_ a PublicAddress as inputs with a couple of small changes we can derive the public address from the view key so that we don't need to pass both of them around this simplifies the signatures for build from TypeScript through napi and into Rust * fixes slow unsigned test * fixes lint --- ironfish-rust-nodejs/index.d.ts | 2 +- ironfish-rust-nodejs/src/structs/transaction.rs | 3 --- ironfish-rust-nodejs/tests/unsigned.test.slow.ts | 1 - ironfish-rust/src/keys/mod.rs | 2 +- ironfish-rust/src/keys/view_keys.rs | 12 ++++++++++++ ironfish-rust/src/transaction/mod.rs | 7 ++----- ironfish-rust/src/transaction/tests.rs | 3 --- ironfish/src/primitives/rawTransaction.ts | 2 -- ironfish/src/wallet/wallet.test.slow.ts | 1 - 9 files changed, 16 insertions(+), 17 deletions(-) diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index e080758828..389b745bdf 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -238,7 +238,7 @@ export class Transaction { * aka: self.value_balance - intended_transaction_fee - change = 0 */ post(spenderHexKey: string, changeGoesTo: string | undefined | null, intendedTransactionFee: bigint): Buffer - build(proofGenerationKeyStr: string, viewKeyStr: string, outgoingViewKeyStr: string, publicAddressStr: string, intendedTransactionFee: bigint, changeGoesTo?: string | undefined | null): Buffer + build(proofGenerationKeyStr: string, viewKeyStr: string, outgoingViewKeyStr: string, intendedTransactionFee: bigint, changeGoesTo?: string | undefined | null): Buffer setExpiration(sequence: number): void } export type NativeUnsignedTransaction = UnsignedTransaction diff --git a/ironfish-rust-nodejs/src/structs/transaction.rs b/ironfish-rust-nodejs/src/structs/transaction.rs index 52ccc9182b..fbf57f5975 100644 --- a/ironfish-rust-nodejs/src/structs/transaction.rs +++ b/ironfish-rust-nodejs/src/structs/transaction.rs @@ -329,14 +329,12 @@ impl NativeTransaction { proof_generation_key_str: String, view_key_str: String, outgoing_view_key_str: String, - public_address_str: String, intended_transaction_fee: BigInt, change_goes_to: Option, ) -> Result { let view_key = ViewKey::from_hex(&view_key_str).map_err(to_napi_err)?; let outgoing_view_key = OutgoingViewKey::from_hex(&outgoing_view_key_str).map_err(to_napi_err)?; - let public_address = PublicAddress::from_hex(&public_address_str).map_err(to_napi_err)?; let proof_generation_key = ProofGenerationKey::from_hex(&proof_generation_key_str) .map_err(|_| to_napi_err("PublicKeyPackage hex to bytes failed"))?; let change_address = match change_goes_to { @@ -349,7 +347,6 @@ impl NativeTransaction { proof_generation_key, view_key, outgoing_view_key, - public_address, intended_transaction_fee.get_i64().0, change_address, ) diff --git a/ironfish-rust-nodejs/tests/unsigned.test.slow.ts b/ironfish-rust-nodejs/tests/unsigned.test.slow.ts index 59f4827b20..023b7e4f41 100644 --- a/ironfish-rust-nodejs/tests/unsigned.test.slow.ts +++ b/ironfish-rust-nodejs/tests/unsigned.test.slow.ts @@ -16,7 +16,6 @@ describe('UnsignedTransaction', () => { key.proofGenerationKey, key.viewKey, key.outgoingViewKey, - key.publicAddress, 0n, ) diff --git a/ironfish-rust/src/keys/mod.rs b/ironfish-rust/src/keys/mod.rs index 97cbf98535..f621686398 100644 --- a/ironfish-rust/src/keys/mod.rs +++ b/ironfish-rust/src/keys/mod.rs @@ -243,7 +243,7 @@ impl SaplingKey { /// /// This method is only called once, but it's kind of messy, so I pulled it /// out of the constructor for easier maintenance. - fn hash_viewing_key( + pub fn hash_viewing_key( authorizing_key: &SubgroupPoint, nullifier_deriving_key: &SubgroupPoint, ) -> Result { diff --git a/ironfish-rust/src/keys/view_keys.rs b/ironfish-rust/src/keys/view_keys.rs index b673ea8c74..f26bcdfc3f 100644 --- a/ironfish-rust/src/keys/view_keys.rs +++ b/ironfish-rust/src/keys/view_keys.rs @@ -16,6 +16,7 @@ use super::PublicAddress; use crate::{ errors::{IronfishError, IronfishErrorKind}, serializing::{bytes_to_hex, hex_to_bytes, read_scalar}, + SaplingKey, }; use bip39::{Language, Mnemonic}; use blake2b_simd::Params as Blake2b; @@ -136,6 +137,17 @@ impl ViewKey { result[32..].copy_from_slice(&self.nullifier_deriving_key.to_bytes()); result } + + pub fn public_address(&self) -> Result { + let ivk = IncomingViewKey { + view_key: SaplingKey::hash_viewing_key( + &self.authorizing_key, + &self.nullifier_deriving_key, + )?, + }; + + Ok(ivk.public_address()) + } } /// Key that allows someone to view a transaction that you have spent. diff --git a/ironfish-rust/src/transaction/mod.rs b/ironfish-rust/src/transaction/mod.rs index 149fb43dde..7d3f3febd2 100644 --- a/ironfish-rust/src/transaction/mod.rs +++ b/ironfish-rust/src/transaction/mod.rs @@ -234,10 +234,11 @@ impl ProposedTransaction { proof_generation_key: ProofGenerationKey, view_key: ViewKey, outgoing_view_key: OutgoingViewKey, - public_address: PublicAddress, intended_transaction_fee: i64, change_goes_to: Option, ) -> Result { + let public_address = view_key.public_address()?; + // skip adding change notes if this is special case of a miners fee transaction let is_miners_fee = self.outputs.iter().any(|output| output.get_is_miners_fee()); if !is_miners_fee { @@ -333,15 +334,12 @@ impl ProposedTransaction { change_goes_to: Option, intended_transaction_fee: u64, ) -> Result { - let public_address = spender_key.public_address(); - let i64_fee = i64::try_from(intended_transaction_fee)?; let unsigned = self.build( spender_key.sapling_proof_generation_key(), spender_key.view_key().clone(), spender_key.outgoing_view_key().clone(), - public_address, i64_fee, change_goes_to, )?; @@ -382,7 +380,6 @@ impl ProposedTransaction { spender_key.sapling_proof_generation_key(), spender_key.view_key().clone(), spender_key.outgoing_view_key().clone(), - spender_key.public_address(), *self.value_balances.fee(), None, )?; diff --git a/ironfish-rust/src/transaction/tests.rs b/ironfish-rust/src/transaction/tests.rs index 03eaa38459..b7815f3b60 100644 --- a/ironfish-rust/src/transaction/tests.rs +++ b/ironfish-rust/src/transaction/tests.rs @@ -243,7 +243,6 @@ fn test_proposed_transaction_build() { spender_key.sapling_proof_generation_key(), spender_key.view_key().clone(), spender_key.outgoing_view_key().clone(), - spender_key.public_address(), intended_fee, Some(public_address), ) @@ -687,7 +686,6 @@ fn test_sign_simple() { spender_key.sapling_proof_generation_key(), spender_key.view_key().clone(), spender_key.outgoing_view_key().clone(), - spender_key.public_address(), 1, Some(spender_key.public_address()), ) @@ -782,7 +780,6 @@ fn test_sign_frost() { key_packages.proof_generation_key, key_packages.view_key, key_packages.outgoing_view_key, - key_packages.public_address, intended_fee, Some(key_packages.public_address), ) diff --git a/ironfish/src/primitives/rawTransaction.ts b/ironfish/src/primitives/rawTransaction.ts index da7480db51..42846856ba 100644 --- a/ironfish/src/primitives/rawTransaction.ts +++ b/ironfish/src/primitives/rawTransaction.ts @@ -166,7 +166,6 @@ export class RawTransaction { proofGenerationKey: string, viewKey: string, outgoingViewKey: string, - publicAddress: string, ): UnsignedTransaction { const builder = this._build() @@ -174,7 +173,6 @@ export class RawTransaction { proofGenerationKey, viewKey, outgoingViewKey, - publicAddress, this.fee, null, ) diff --git a/ironfish/src/wallet/wallet.test.slow.ts b/ironfish/src/wallet/wallet.test.slow.ts index 354288e363..5a71a543ec 100644 --- a/ironfish/src/wallet/wallet.test.slow.ts +++ b/ironfish/src/wallet/wallet.test.slow.ts @@ -1279,7 +1279,6 @@ describe('Wallet', () => { trustedDealerPackage.proofGenerationKey, trustedDealerPackage.viewKey, trustedDealerPackage.outgoingViewKey, - trustedDealerPackage.publicAddress, ) const signingPackage = unsignedTransaction.signingPackage(signingCommitments) From 411858c0878e92fe0481efa07180b6c0160d15ea Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Mon, 29 Jan 2024 13:21:42 -0800 Subject: [PATCH 05/27] Convert unspentNoteHashes to PrefixArrayEncoding (#4608) --- .../src/migrations/data/024-unspent-notes.ts | 5 +-- .../data/024-unspent-notes/new/index.ts | 30 +++++-------- .../data/030-value-to-unspent-note.ts | 5 ++- .../030-value-to-unspent-note/old/index.ts | 30 +++++-------- ironfish/src/wallet/walletdb/walletdb.ts | 43 ++++++------------- 5 files changed, 37 insertions(+), 76 deletions(-) diff --git a/ironfish/src/migrations/data/024-unspent-notes.ts b/ironfish/src/migrations/data/024-unspent-notes.ts index d68bd4b1cc..ef9b62fbb4 100644 --- a/ironfish/src/migrations/data/024-unspent-notes.ts +++ b/ironfish/src/migrations/data/024-unspent-notes.ts @@ -52,10 +52,7 @@ export class Migration024 extends Migration { } await stores.new.unspentNoteHashes.put( - [ - account.prefix, - [note.note.assetId(), [note.sequence, [note.note.value(), noteHash]]], - ], + [account.prefix, note.note.assetId(), note.sequence, note.note.value(), noteHash], null, ) unspentNotes++ diff --git a/ironfish/src/migrations/data/024-unspent-notes/new/index.ts b/ironfish/src/migrations/data/024-unspent-notes/new/index.ts index d95430f0a7..0f32cd175d 100644 --- a/ironfish/src/migrations/data/024-unspent-notes/new/index.ts +++ b/ironfish/src/migrations/data/024-unspent-notes/new/index.ts @@ -7,39 +7,29 @@ import { IDatabase, IDatabaseStore, NULL_ENCODING, - PrefixEncoding, + PrefixArrayEncoding, U32_ENCODING_BE, } from '../../../../storage' import { Account } from '../../../../wallet' export function GetNewStores(db: IDatabase): { unspentNoteHashes: IDatabaseStore<{ - key: [Account['prefix'], [Buffer, [number, [bigint, Buffer]]]] + key: [Account['prefix'], Buffer, number, bigint, Buffer] value: null }> } { const unspentNoteHashes: IDatabaseStore<{ - key: [Account['prefix'], [Buffer, [number, [bigint, Buffer]]]] + key: [Account['prefix'], Buffer, number, bigint, Buffer] value: null }> = db.addStore({ name: 'un', - keyEncoding: new PrefixEncoding( - new BufferEncoding(), // account prefix - new PrefixEncoding( - new BufferEncoding(), // asset ID - new PrefixEncoding( - U32_ENCODING_BE, // sequence - new PrefixEncoding( - new BigU64BEEncoding(), // value - new BufferEncoding(), // note hash - 8, - ), - 4, - ), - 32, - ), - 4, - ), + keyEncoding: new PrefixArrayEncoding([ + [new BufferEncoding(), 4], // account prefix + [new BufferEncoding(), 32], // asset ID + [U32_ENCODING_BE, 4], // sequence + [new BigU64BEEncoding(), 8], // value + [new BufferEncoding(), 32], // note hash + ]), valueEncoding: NULL_ENCODING, }) diff --git a/ironfish/src/migrations/data/030-value-to-unspent-note.ts b/ironfish/src/migrations/data/030-value-to-unspent-note.ts index e58beca3a2..7f67dae298 100644 --- a/ironfish/src/migrations/data/030-value-to-unspent-note.ts +++ b/ironfish/src/migrations/data/030-value-to-unspent-note.ts @@ -30,7 +30,10 @@ export class Migration030 extends Migration { for await (const [ prefix, - [assetId, [_sequence, [value, noteHash]]], + assetId, + , + value, + noteHash, ] of stores.old.unspentNoteHashes.getAllKeysIter()) { await stores.new.valueToUnspentNoteHash.put([prefix, assetId, value, noteHash], null) unspentNotes++ diff --git a/ironfish/src/migrations/data/030-value-to-unspent-note/old/index.ts b/ironfish/src/migrations/data/030-value-to-unspent-note/old/index.ts index 84f83de8e7..aaedb73677 100644 --- a/ironfish/src/migrations/data/030-value-to-unspent-note/old/index.ts +++ b/ironfish/src/migrations/data/030-value-to-unspent-note/old/index.ts @@ -7,39 +7,29 @@ import { IDatabase, IDatabaseStore, NULL_ENCODING, - PrefixEncoding, + PrefixArrayEncoding, U32_ENCODING_BE, } from '../../../../storage' import { Account } from '../../../../wallet' export function GetOldStores(db: IDatabase): { unspentNoteHashes: IDatabaseStore<{ - key: [Account['prefix'], [Buffer, [number, [bigint, Buffer]]]] + key: [Account['prefix'], Buffer, number, bigint, Buffer] value: null }> } { const unspentNoteHashes: IDatabaseStore<{ - key: [Account['prefix'], [Buffer, [number, [bigint, Buffer]]]] + key: [Account['prefix'], Buffer, number, bigint, Buffer] value: null }> = db.addStore({ name: 'un', - keyEncoding: new PrefixEncoding( - new BufferEncoding(), // account prefix - new PrefixEncoding( - new BufferEncoding(), // asset ID - new PrefixEncoding( - U32_ENCODING_BE, // sequence - new PrefixEncoding( - new BigU64BEEncoding(), // value - new BufferEncoding(), // note hash - 8, - ), - 4, - ), - 32, - ), - 4, - ), + keyEncoding: new PrefixArrayEncoding([ + [new BufferEncoding(), 4], // account prefix + [new BufferEncoding(), 32], // asset ID + [U32_ENCODING_BE, 4], // sequence + [new BigU64BEEncoding(), 8], // value + [new BufferEncoding(), 32], // note hash + ]), valueEncoding: NULL_ENCODING, }) diff --git a/ironfish/src/wallet/walletdb/walletdb.ts b/ironfish/src/wallet/walletdb/walletdb.ts index d335519c0f..be34c0a0b8 100644 --- a/ironfish/src/wallet/walletdb/walletdb.ts +++ b/ironfish/src/wallet/walletdb/walletdb.ts @@ -125,7 +125,7 @@ export class WalletDB { }> unspentNoteHashes: IDatabaseStore<{ - key: [Account['prefix'], [Buffer, [number, [bigint, Buffer]]]] + key: [Account['prefix'], Buffer, number, bigint, Buffer] value: null }> @@ -261,23 +261,13 @@ export class WalletDB { this.unspentNoteHashes = this.db.addStore({ name: 'un', - keyEncoding: new PrefixEncoding( - new BufferEncoding(), // account prefix - new PrefixEncoding( - new BufferEncoding(), // asset ID - new PrefixEncoding( - U32_ENCODING_BE, // sequence - new PrefixEncoding( - new BigU64BEEncoding(), // value - new BufferEncoding(), // note hash - 8, - ), - 4, - ), - 32, - ), - 4, - ), + keyEncoding: new PrefixArrayEncoding([ + [new BufferEncoding(), 4], // account prefix + [new BufferEncoding(), 32], // asset ID + [U32_ENCODING_BE, 4], // sequence + [new BigU64BEEncoding(), 8], // value + [new BufferEncoding(), 32], // note hash + ]), valueEncoding: NULL_ENCODING, }) @@ -601,7 +591,7 @@ export class WalletDB { const value = decryptedNote.note.value() await this.unspentNoteHashes.put( - [account.prefix, [assetId, [sequence, [value, noteHash]]]], + [account.prefix, assetId, sequence, value, noteHash], null, tx, ) @@ -625,10 +615,7 @@ export class WalletDB { Assert.isNotNull(sequence, 'Cannot spend a note that is not on the chain.') - await this.unspentNoteHashes.del( - [account.prefix, [assetId, [sequence, [value, noteHash]]]], - tx, - ) + await this.unspentNoteHashes.del([account.prefix, assetId, sequence, value, noteHash], tx) await this.valueToUnspentNoteHashes.del([account.prefix, assetId, value, noteHash], tx) } @@ -682,10 +669,7 @@ export class WalletDB { encoding.serialize([account.prefix, [assetId, maxConfirmedSequence]]), ) - for await (const [, [, [, [_, noteHash]]]] of this.unspentNoteHashes.getAllKeysIter( - tx, - range, - )) { + for await (const [, , , , noteHash] of this.unspentNoteHashes.getAllKeysIter(tx, range)) { yield noteHash } } @@ -724,10 +708,7 @@ export class WalletDB { encoding.serialize([account.prefix, [assetId, maxConfirmedSequence]]), ) - for await (const [, [, [, [value, _]]]] of this.unspentNoteHashes.getAllKeysIter( - tx, - range, - )) { + for await (const [, , , value, _] of this.unspentNoteHashes.getAllKeysIter(tx, range)) { yield value } } From f40bbf8f6c73c0cd52a5ad10a751ba9795a4366c Mon Sep 17 00:00:00 2001 From: jowparks Date: Mon, 29 Jan 2024 14:58:44 -0800 Subject: [PATCH 06/27] adds createSigningPackage RPC, minor refactors in other areas (#4618) --- ironfish/src/rpc/clients/client.ts | 40 +++++++++-- .../createSigningPackage.test.ts.fixture | 51 ++++++++++++++ .../multisig/createSigningCommitment.test.ts | 12 ++-- .../multisig/createSigningPackage.test.ts | 69 +++++++++++++++++++ .../routes/multisig/createSigningPackage.ts | 66 ++++++++++++++++++ .../createTrustedDealerKeyPackage.test.ts | 10 +-- ironfish/src/rpc/routes/multisig/index.ts | 1 + .../testUtilities/fixtures/transactions.ts | 45 +++++++++++- 8 files changed, 274 insertions(+), 20 deletions(-) create mode 100644 ironfish/src/rpc/routes/multisig/__fixtures__/createSigningPackage.test.ts.fixture create mode 100644 ironfish/src/rpc/routes/multisig/createSigningPackage.test.ts create mode 100644 ironfish/src/rpc/routes/multisig/createSigningPackage.ts diff --git a/ironfish/src/rpc/clients/client.ts b/ironfish/src/rpc/clients/client.ts index de3b094880..3ed26a4b99 100644 --- a/ironfish/src/rpc/clients/client.ts +++ b/ironfish/src/rpc/clients/client.ts @@ -17,8 +17,14 @@ import type { BurnAssetResponse, CreateAccountRequest, CreateAccountResponse, + CreateSigningCommitmentRequest, + CreateSigningCommitmentResponse, + CreateSigningPackageRequest, + CreateSigningPackageResponse, CreateTransactionRequest, CreateTransactionResponse, + CreateTrustedDealerKeyPackageRequest, + CreateTrustedDealerKeyPackageResponse, EstimateFeeRateRequest, EstimateFeeRateResponse, EstimateFeeRatesRequest, @@ -35,6 +41,8 @@ import type { GetAccountsResponse, GetAccountsStatusRequest, GetAccountsStatusResponse, + GetAccountStatusRequest, + GetAccountStatusResponse, GetAccountTransactionRequest, GetAccountTransactionResponse, GetAccountTransactionsRequest, @@ -132,10 +140,6 @@ import type { UseAccountResponse, } from '../routes' import { ApiNamespace } from '../routes/namespaces' -import { - GetAccountStatusRequest, - GetAccountStatusResponse, -} from '../routes/wallet/getAccountStatus' export abstract class RpcClient { abstract request( @@ -803,4 +807,32 @@ export abstract class RpcClient { ).waitForEnd() }, } + multisig = { + createTrustedDealerKeyPackage: ( + params: CreateTrustedDealerKeyPackageRequest, + ): Promise> => { + return this.request( + `${ApiNamespace.multisig}/createTrustedDealerKeyPackage`, + params, + ).waitForEnd() + }, + + createSigningPackage: ( + params: CreateSigningPackageRequest, + ): Promise> => { + return this.request( + `${ApiNamespace.multisig}/createSigningPackage`, + params, + ).waitForEnd() + }, + + createSigningCommitment: ( + params: CreateSigningCommitmentRequest, + ): Promise> => { + return this.request( + `${ApiNamespace.multisig}/createSigningCommitment`, + params, + ).waitForEnd() + }, + } } diff --git a/ironfish/src/rpc/routes/multisig/__fixtures__/createSigningPackage.test.ts.fixture b/ironfish/src/rpc/routes/multisig/__fixtures__/createSigningPackage.test.ts.fixture new file mode 100644 index 0000000000..1fa3018bc3 --- /dev/null +++ b/ironfish/src/rpc/routes/multisig/__fixtures__/createSigningPackage.test.ts.fixture @@ -0,0 +1,51 @@ +{ + "Route multisig/createSigningPackage should create signing package": [ + { + "version": 3, + "id": "c7b0571b-0f15-401a-976e-788da2104081", + "name": "test", + "spendingKey": "3b12e9d3dbb69b824110bdff03e75c8ca1dfd1bd36ac6fba29f1b44f7bc0289f", + "viewKey": "aa59653f676b2ae91df96373c0253f8be3ab617f9ad00b5e99bb5eddeb0104cca531b3cf2d128d2dd2faf0f2a565185de18e1d189415406e560da54263b5e28c", + "incomingViewKey": "ae34aca726e88d23540186859785ce2d8227faa17977f895e3f1118dd160d302", + "outgoingViewKey": "459ec04e08888cdf197b797c51465a61651ada3f67c7feab4545ffe8cc3f24e4", + "publicAddress": "4adaa758c9b56e6ba43cb5b2145e4cfb2cd452bd465907c2d793bf24532db631", + "createdAt": { + "hash": { + "type": "Buffer", + "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" + }, + "sequence": 1 + } + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", + "noteCommitment": { + "type": "Buffer", + "data": "base64:vaK33l3E/GmTTFDrGd2UtxfEDTKnLQY28V+ih3Si/24=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:ufZmlvhr5ZtCO3/XymAQWSmPPTQLa+hU54481nEs4dg=" + }, + "target": "9282972777491357380673661573939192202192629606981189395159182914949423", + "randomness": "0", + "timestamp": 1706309118482, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AgAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAFHNAEBkCnTEIA3pRYFGgT/ipR7SLYCKYCY6XKmMY472s+VDxRfCXrSHUgAGLs7G7eN03RgNPaj6gaD5EP1zJc6OsGoJELGoih6SQm/IwsWqMdLU14LVtPaVaS4lrTXARaE7d/YY/JvA3Ja9xb2DqRZo9Eovv86YQg9R5zp9ZVMQVuL9SwJbRAoqYJaODy8lgKCCn3Ec0fZMwqZtmc89ASConpI0c/KttzEHRl48Kk+GSuhYDbSj/gyUOTEDkftOUjVsPh8jgII13y/TkdmskMqGpKyNh85mu/lhWkx0GveWNgFWIP6diQDvAlRpo9o93rns4IZsRnZrzxLa+gp4jwZ9SEMO5mjLvDTisUbl75v/5zhNXbYzoYNbpcGkUr38ueaEoGu9PTlH8skhV5dJuMQ9X2UM8wzqp44n8uuEQ2LR2krg8jCGwqilVWYmiFYyWemn5G270dPEQMi6KUjOCwCMTwNHuMEzp85jnMTFen4GdXabXoAPXvUi46pK15OIwUVLZxrS/O+1Edjy8dsy/aECqDs4Zv0czLkH/m7dEZdVIvkd6KSeAIYiaiQrSLrbJv+jBltX+B8Vf52OI5i5lJXpjivWHQBH3MRXbGx3HOBNPqM4X7aOHkklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwHjjydQEIy3hNZFVS0N/K9WYFzNBnsHwfy/hEeLWZjVyrLA/UwTN459cYzDHP5swmBM9dWgfMfVBcbbUah0h4Bw==" + } + ] + }, + { + "type": "Buffer", + "data": "base64:AgEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY4PWRSWfZBXikAz3xPf9b4aZauTLwiuxT8+wiKSTjyJsrCf0Qq9khrLpAhUE3e+qI5m9STHe4DhXQent1li/C2ysJ/RCr2SGsukCFQTd76ojmb1JMd7gOFdB6e3WWL8LuXBvLWBLBdJ7DwrCgYRk78sLaRbXdwecA9MgWJs6YgGpiO6B0M/xDT9zrAqQAbwDkkM1YNds5uGMHcKVbFrPUQRwMFt4BywJBdZ6dqsYlDeKJZVAnpwNC7e08kJoCpqPB3F7/DFBDM+559bw4+OZr90V/ToI+Ihzrp0vnSYoc40iRxc+O+/L3nABWJcBw5CrqezcLqGs9fcXfWqzjjNqqckrl7eC2naM6ZNt8ozB+veEd1+1duQbvPsa08CSSHev0PdehYS3JlGvwr31fB4Rwrhu3uWdLP3+RU7dKyAWNtO9orfeXcT8aZNMUOsZ3ZS3F8QNMqctBjbxX6KHdKL/bgQAAADUKVqPY5OMPk4KxBTT+kCt9y+qcpwYO0PYTSJmEdTdjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACqPiDSNBm5nG/dPjP8In++HLcUMXe6gXcqKPxwrRxqU7ZbGHF0qD70YOPLX2TfDvSooZIoNI3CipVlqUpAyTk1hGW+h/cRnGzXb8KNqn52KgpoWu7+w+lmNBlVr1eJWsEIWHQMBEZLQQGFZBIJQbrehbAIQIEMDna7hb8S26Y9wGZ4FZCCNRSeVrzx9s4JP32OK1IxHXS4UulcoC09/lNc2fusFgz0rVPISvKj/zWS14haqg9VVW3GSxFOaoGc6tcuJXYixk1JIDBUasI2eHGi26rCA3zkHUF7f25RoFSy8eWbBKjkAdq6iIEh+btD0jE1cFD5WWjjWm6vlQKop04tU14wgqOlJxFmAsWSAEFuEefWLQ4XKIELb25MECBTuYDpZ1gc0FlHrjogCumekb3oxG1X8+gwFe/dBZ1I+thdaEjsBwGCubtgf6e6OKhAsi3tuSQkq+ueSezEr/BoiY/iKTBegyyS4CCntdLfH9hupvFUvZ/2FbxwnE8nHKYsecE/bY8C46O+XHGoRSn80Z1HsATGloYuviODYiUZvz/LAWEAMzQCgz2FXZNi4QhbvgMooNGLgMoa8lhEgK5GB5qgz8w9023yO1g3MzW6YMZNYj+GT2VvE6j5LC206dPQQad+FHeteMYF0/9904J2T+eCkW7JJ118PyK0LQ6lLfRbogGpx4KbbEFHtlFJvHHqNgeydH3sDJ/e/FwzNfENZZKC/MvB1rHNqtjHYnxD8qy7OXkE066GIZvdijrtbYa8tNlGZkYLi8wzC467Ut951j9NWPdy1iLx20fjIo/maZ99txFa3X9pZo7OACwPD3j989GFq2S/Xm/75+DDL4Ti57Km8vLqmUcWe819ZuBmbhXZT7zg3Mg7mkyLowdMvTdS7QrV1YmucxGsB9JmOCuM5yXjZiImoXYHARzZWw7wiWn+HyUG92su4ZGSRIbUk0b0zW8KRNEsnitE381e5XWIwzU4ldXwJ/z8zakDGUXvJN/wdbdBw5Y/g6Neo6nooXY4PJgvTv21JAjdScfCPTALF1wDwZpUxZzTzJj5skzf7vWq3S/4objNLApS8PGa0lAs5DF3rRljv4Oa9suGOJPe6yqcdydBEffOt3MmzsD7u2rzl6W21soFhLnVuiCRhus/7VSPx5//JpQ+QPSC7ucexCvJGuFb1YpWVCJX2CZJpoNrqcxAWvuOtdtAaRp53AVFfPdQ5G14d2qTktCWgiEzZ9gj2+3aPzHTqSvT0XhG85Ap4uamadYvF0DnIMRzotJMoN92meWpSoRBlFLH51cupA6GDodKNj5L/sjfk2d+Cupy2MNv3DSghpf8WrbeNhJCptPQvbctJTPzeT1pPuQAO2LekaX5Y+LSJmwKuipiXj1kmU7sz6jAvhncOVFDpJ9HvJyfhhdeyaQX6UqeKg5z8ZiFiXwM4JWWS5VbO5yDT3RcqoTNFr9ngpGgZi3qqrqEiAA=" + } + ] +} \ No newline at end of file diff --git a/ironfish/src/rpc/routes/multisig/createSigningCommitment.test.ts b/ironfish/src/rpc/routes/multisig/createSigningCommitment.test.ts index 5661076faa..761544aa97 100644 --- a/ironfish/src/rpc/routes/multisig/createSigningCommitment.test.ts +++ b/ironfish/src/rpc/routes/multisig/createSigningCommitment.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 { ParticipantSecret, TrustedDealerKeyPackages } from '@ironfish/rust-nodejs' +import { ParticipantSecret } from '@ironfish/rust-nodejs' import { createRouteTest } from '../../../testUtilities/routeTest' describe('Route multisig/createSigningCommitment', () => { @@ -9,9 +9,7 @@ describe('Route multisig/createSigningCommitment', () => { it('should error on invalid keypackage', async () => { const keyPackage = 'invalid key package' - const request = { keyPackage, seed: 0 } - await expect( routeTest.client.request('multisig/createSigningCommitment', request).waitForEnd(), ).rejects.toThrow('InvalidData') @@ -23,15 +21,13 @@ describe('Route multisig/createSigningCommitment', () => { })) const request = { minSigners: 2, maxSigners: 3, participants } - const response = await routeTest.client - .request('multisig/createTrustedDealerKeyPackage', request) - .waitForEnd() + const response = await routeTest.client.multisig.createTrustedDealerKeyPackage(request) - const trustedDealerKeyPackage = response.content as TrustedDealerKeyPackages + const trustedDealerPackage = response.content const signingCommitment = await routeTest.client .request('multisig/createSigningCommitment', { - keyPackage: trustedDealerKeyPackage.keyPackages[0].keyPackage, + keyPackage: trustedDealerPackage.keyPackages[0].keyPackage, seed: 420, }) .waitForEnd() diff --git a/ironfish/src/rpc/routes/multisig/createSigningPackage.test.ts b/ironfish/src/rpc/routes/multisig/createSigningPackage.test.ts new file mode 100644 index 0000000000..969eb3ef9d --- /dev/null +++ b/ironfish/src/rpc/routes/multisig/createSigningPackage.test.ts @@ -0,0 +1,69 @@ +/* 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 { ParticipantSecret, SigningCommitments } from '@ironfish/rust-nodejs' +import { + createNodeTest, + useAccountFixture, + useMinerBlockFixture, + useUnsignedTxFixture, +} from '../../../testUtilities' +import { createRouteTest } from '../../../testUtilities/routeTest' + +describe('Route multisig/createSigningPackage', () => { + const routeTest = createRouteTest() + const nodeTest = createNodeTest() + + it('should create signing package', async () => { + const seed = 420 + + const participants = Array.from({ length: 3 }, () => ({ + identifier: ParticipantSecret.random().toIdentity().toFrostIdentifier(), + })) + + const request = { + minSigners: 2, + maxSigners: 3, + participants, + } + const response = await routeTest.client.multisig.createTrustedDealerKeyPackage(request) + + const trustedDealerPackage = response.content + + const commitments: Array<{ identifier: string; commitment: SigningCommitments }> = [] + for (let i = 0; i < 3; i++) { + const commitment = await routeTest.client.multisig.createSigningCommitment({ + keyPackage: trustedDealerPackage.keyPackages[i].keyPackage, + seed, + }) + + commitments.push({ + identifier: trustedDealerPackage.keyPackages[i].identifier, + commitment: commitment.content, + }) + } + + const account = await useAccountFixture(nodeTest.wallet) + + // fund account + const block = await useMinerBlockFixture( + nodeTest.chain, + undefined, + account, + nodeTest.wallet, + ) + await nodeTest.chain.addBlock(block) + await nodeTest.wallet.updateHead() + + const unsignedTransaction = await useUnsignedTxFixture(nodeTest.wallet, account, account) + const unsignedString = unsignedTransaction.serialize().toString('hex') + const responseSigningPackage = await routeTest.client.multisig.createSigningPackage({ + commitments, + unsignedTransaction: unsignedString, + }) + + expect(responseSigningPackage.content).toMatchObject({ + signingPackage: expect.any(String), + }) + }) +}) diff --git a/ironfish/src/rpc/routes/multisig/createSigningPackage.ts b/ironfish/src/rpc/routes/multisig/createSigningPackage.ts new file mode 100644 index 0000000000..cc9c4b1c48 --- /dev/null +++ b/ironfish/src/rpc/routes/multisig/createSigningPackage.ts @@ -0,0 +1,66 @@ +/* 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 { SigningCommitments, UnsignedTransaction } from '@ironfish/rust-nodejs' +import * as yup from 'yup' +import { ApiNamespace } from '../namespaces' +import { routes } from '../router' +import { RpcSigningCommitments, RpcSigningCommitmentsSchema } from './types' + +export type CreateSigningPackageRequest = { + unsignedTransaction: string + commitments: Array<{ + identifier: string + commitment: RpcSigningCommitments + }> +} + +export type CreateSigningPackageResponse = { + signingPackage: string +} + +export const CreateSigningPackageRequestSchema: yup.ObjectSchema = + yup + .object({ + unsignedTransaction: yup.string().defined(), + commitments: yup + .array( + yup + .object({ + identifier: yup.string().defined(), + commitment: RpcSigningCommitmentsSchema, + }) + .defined(), + ) + .defined(), + }) + .defined() + +export const CreateSigningPackageResponseSchema: yup.ObjectSchema = + yup + .object({ + signingPackage: yup.string().defined(), + }) + .defined() + +routes.register( + `${ApiNamespace.multisig}/createSigningPackage`, + CreateSigningPackageRequestSchema, + (request, _context): void => { + const unsignedTransaction = new UnsignedTransaction( + Buffer.from(request.data.unsignedTransaction, 'hex'), + ) + const map = request.data.commitments.reduce( + (acc: { [key: string]: SigningCommitments }, { identifier, commitment }) => { + acc[identifier] = commitment + return acc + }, + {}, + ) + const signingPackage = unsignedTransaction.signingPackage(map) + + request.end({ + signingPackage, + }) + }, +) diff --git a/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.test.ts b/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.test.ts index 21f82ebdcd..1b16af75c1 100644 --- a/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.test.ts +++ b/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.test.ts @@ -8,13 +8,9 @@ describe('Route multisig/createTrustedDealerKeyPackage', () => { const routeTest = createRouteTest() it('should create trusted dealer key package', async () => { - const participants: { identifier: string }[] = [] - for (let i = 0; i < 3; i++) { - const identifier = ParticipantSecret.random().toIdentity().toFrostIdentifier() - participants.push({ - identifier: identifier, - }) - } + const participants = Array.from({ length: 3 }, () => ({ + identifier: ParticipantSecret.random().toIdentity().toFrostIdentifier(), + })) const request = { minSigners: 2, maxSigners: 3, participants } const response = await routeTest.client .request('multisig/createTrustedDealerKeyPackage', request) diff --git a/ironfish/src/rpc/routes/multisig/index.ts b/ironfish/src/rpc/routes/multisig/index.ts index 906cbdef22..331694affc 100644 --- a/ironfish/src/rpc/routes/multisig/index.ts +++ b/ironfish/src/rpc/routes/multisig/index.ts @@ -3,4 +3,5 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ export * from './createSigningCommitment' +export * from './createSigningPackage' export * from './createTrustedDealerKeyPackage' diff --git a/ironfish/src/testUtilities/fixtures/transactions.ts b/ironfish/src/testUtilities/fixtures/transactions.ts index 66e1b5e27c..00d8c3926a 100644 --- a/ironfish/src/testUtilities/fixtures/transactions.ts +++ b/ironfish/src/testUtilities/fixtures/transactions.ts @@ -1,12 +1,13 @@ /* 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 { Asset } from '@ironfish/rust-nodejs' +import { Asset, generateKeyFromPrivateKey } from '@ironfish/rust-nodejs' import { Assert } from '../../assert' import { FullNode } from '../../node' import { BurnDescription } from '../../primitives/burnDescription' import { MintData } from '../../primitives/rawTransaction' import { SerializedTransaction, Transaction } from '../../primitives/transaction' +import { UnsignedTransaction } from '../../primitives/unsignedTransaction' import { Account, Wallet } from '../../wallet' import { createRawTransaction } from '../helpers/transaction' import { useAccountFixture } from './account' @@ -99,6 +100,48 @@ export async function useTxFixture( }) } +export async function useUnsignedTxFixture( + wallet: Wallet, + from: Account, + to: Account, + generate?: FixtureGenerate, + fee?: bigint, + expiration?: number, +): Promise { + generate = + generate || + (async () => { + const raw = await wallet.createTransaction({ + account: from, + outputs: [ + { + publicAddress: to.publicAddress, + amount: BigInt(1), + memo: '', + assetId: Asset.nativeId(), + }, + ], + fee: fee ?? 0n, + expiration: expiration ?? 0, + expirationDelta: 0, + }) + Assert.isNotNull(from.spendingKey) + const key = generateKeyFromPrivateKey(from.spendingKey) + const unsignedBuffer = raw + .build(key.proofGenerationKey, key.viewKey, key.outgoingViewKey, key.publicAddress) + .serialize() + return new UnsignedTransaction(unsignedBuffer) + }) + return useFixture(generate, { + serialize: (tx: UnsignedTransaction): Buffer => { + return tx.serialize() + }, + deserialize: (tx: Buffer): UnsignedTransaction => { + return new UnsignedTransaction(tx) + }, + }) +} + export async function useMinersTxFixture( node: FullNode, to?: Account, From 58ae673ddfc1757a35ed2d7581c99f14d5042e39 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:50:23 -0800 Subject: [PATCH 07/27] removes publicAddress from build args in fixture (#4632) one PR added a fixture, another PR changed the arguments of a function that the fixture called when both PRs merged soon after each other, we ended up with a broken build --- ironfish/src/testUtilities/fixtures/transactions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ironfish/src/testUtilities/fixtures/transactions.ts b/ironfish/src/testUtilities/fixtures/transactions.ts index 00d8c3926a..8eb4adc256 100644 --- a/ironfish/src/testUtilities/fixtures/transactions.ts +++ b/ironfish/src/testUtilities/fixtures/transactions.ts @@ -128,7 +128,7 @@ export async function useUnsignedTxFixture( Assert.isNotNull(from.spendingKey) const key = generateKeyFromPrivateKey(from.spendingKey) const unsignedBuffer = raw - .build(key.proofGenerationKey, key.viewKey, key.outgoingViewKey, key.publicAddress) + .build(key.proofGenerationKey, key.viewKey, key.outgoingViewKey) .serialize() return new UnsignedTransaction(unsignedBuffer) }) From 9917b877abb729fe820b97c8ebd0316afbd0c1e9 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Mon, 29 Jan 2024 17:55:05 -0800 Subject: [PATCH 08/27] Move RawBlock/Header constructors onto BockChain (#4633) This is a move in an effort to delete Strategy. --- ironfish/src/blockchain/blockchain.test.ts | 80 +++++++++++++++- ironfish/src/blockchain/blockchain.ts | 24 ++++- ironfish/src/consensus/verifier.test.ts | 34 +++---- ironfish/src/genesis/addGenesisTransaction.ts | 2 +- ironfish/src/genesis/genesis.test.slow.ts | 4 +- ironfish/src/genesis/makeGenesisBlock.ts | 2 +- ironfish/src/mining/manager.test.slow.ts | 8 +- ironfish/src/mining/manager.ts | 4 +- .../src/network/messages/getBlocks.test.ts | 4 +- .../network/messages/getCompactBlock.test.ts | 2 +- ironfish/src/network/peerNetwork.test.ts | 2 +- ironfish/src/network/peerNetwork.ts | 8 +- ironfish/src/node.ts | 9 +- ironfish/src/primitives/block.test.ts | 16 ++-- ironfish/src/primitives/block.ts | 6 +- ironfish/src/primitives/blockheader.test.ts | 56 ++++++----- ironfish/src/primitives/blockheader.ts | 6 +- ironfish/src/serde/BlockTemplateSerde.ts | 6 +- ironfish/src/strategy.test.slow.ts | 95 ++----------------- ironfish/src/strategy.test.ts | 7 +- ironfish/src/strategy.ts | 20 +--- ironfish/src/testUtilities/fixtures/blocks.ts | 2 +- ironfish/src/utils/blockutil.test.ts | 6 +- 23 files changed, 214 insertions(+), 189 deletions(-) diff --git a/ironfish/src/blockchain/blockchain.test.ts b/ironfish/src/blockchain/blockchain.test.ts index 3e61957ea2..cd011a2db1 100644 --- a/ironfish/src/blockchain/blockchain.test.ts +++ b/ironfish/src/blockchain/blockchain.test.ts @@ -6,7 +6,9 @@ import { Asset, generateKey, Note as NativeNote } from '@ironfish/rust-nodejs' import { Assert } from '../assert' import { VerificationResultReason } from '../consensus' import { FullNode } from '../node' -import { Block, Note } from '../primitives' +import { Block, Note, Target } from '../primitives' +import { RawBlock } from '../primitives/block' +import { RawBlockHeader } from '../primitives/blockheader' import { NoteEncrypted } from '../primitives/noteEncrypted' import { RawTransaction } from '../primitives/rawTransaction' import { TransactionVersion } from '../primitives/transaction' @@ -435,7 +437,7 @@ describe('Blockchain', () => { const genesis = nodeTest.chain.genesis expect(node.chain.head?.hash).toEqualBuffer(genesis.hash) - const blockB3Invalid = nodeTest.strategy.newBlock({ + const blockB3Invalid = nodeTest.chain.newBlockFromRaw({ header: { ...blockB3.header, noteCommitment: Buffer.alloc(32), @@ -731,7 +733,7 @@ describe('Blockchain', () => { valid: true, }) - const invalidBlock = nodeTest.strategy.newBlock({ + const invalidBlock = nodeTest.chain.newBlockFromRaw({ header: { ...block.header, timestamp: new Date(0), @@ -1767,6 +1769,78 @@ describe('Blockchain', () => { }) }) + describe('newBlockHeaderFromRaw', () => { + it('should construct and hash block header', async () => { + const { chain } = await nodeTest.createSetup() + + const raw: RawBlockHeader = { + previousBlockHash: Buffer.alloc(32), + noteCommitment: Buffer.alloc(32, 'header'), + transactionCommitment: Buffer.alloc(32, 'transactionRoot'), + target: new Target(17), + randomness: BigInt(25), + timestamp: new Date(1598467858637), + graffiti: Buffer.alloc(32), + sequence: 1, + } + + const hashHeaderSpy = jest.spyOn(chain.blockHasher, 'hashHeader') + + const header = chain.newBlockHeaderFromRaw(raw) + expect(header.sequence).toEqual(1) + expect(hashHeaderSpy).toHaveBeenCalledTimes(1) + }) + + it('Creates block headers with noteSize and work if passed in', async () => { + const { chain } = await nodeTest.createSetup() + + const raw = { + previousBlockHash: Buffer.alloc(32), + noteCommitment: Buffer.alloc(32, 'header'), + transactionCommitment: Buffer.alloc(32, 'transactionRoot'), + target: new Target(17), + randomness: BigInt(25), + timestamp: new Date(1598467858637), + graffiti: Buffer.alloc(32), + sequence: 1, + } + + const header1 = chain.newBlockHeaderFromRaw(raw) + expect(header1.noteSize).toBeNull() + expect(header1.work).toEqual(BigInt(0)) + + const header2 = chain.newBlockHeaderFromRaw(raw, 123, BigInt(456)) + expect(header2.noteSize).toEqual(123) + expect(header2.work).toEqual(BigInt(456)) + }) + }) + + describe('newBlockFromRaw', () => { + it('should construct and hash block', async () => { + const { chain } = await nodeTest.createSetup() + + const raw: RawBlock = { + header: { + previousBlockHash: Buffer.alloc(32), + noteCommitment: Buffer.alloc(32, 'header'), + transactionCommitment: Buffer.alloc(32, 'transactionRoot'), + target: new Target(17), + randomness: BigInt(25), + timestamp: new Date(1598467858637), + graffiti: Buffer.alloc(32), + sequence: 1, + }, + transactions: [], + } + + const hashHeaderSpy = jest.spyOn(chain.blockHasher, 'hashHeader') + + const header = chain.newBlockFromRaw(raw) + expect(header.header.sequence).toEqual(1) + expect(hashHeaderSpy).toHaveBeenCalledTimes(1) + }) + }) + describe('transactionHashToBlockHash', () => { it('should insert records when a block is connected to the main chain', async () => { const { node } = nodeTest diff --git a/ironfish/src/blockchain/blockchain.ts b/ironfish/src/blockchain/blockchain.ts index 5b85380678..3300dd501a 100644 --- a/ironfish/src/blockchain/blockchain.ts +++ b/ironfish/src/blockchain/blockchain.ts @@ -6,6 +6,7 @@ import { Asset } from '@ironfish/rust-nodejs' import LRU from 'blru' import { BufferMap } from 'buffer-map' import { Assert } from '../assert' +import { BlockHasher } from '../blockHasher' import { Consensus } from '../consensus' import { VerificationResultReason, Verifier } from '../consensus/verifier' import { Event } from '../event' @@ -23,6 +24,7 @@ import { BlockSerde, GENESIS_BLOCK_PREVIOUS, GENESIS_BLOCK_SEQUENCE, + RawBlock, SerializedBlock, } from '../primitives/block' import { @@ -30,6 +32,7 @@ import { BlockHeader, isBlockHeavier, isBlockLater, + RawBlockHeader, transactionCommitment, } from '../primitives/blockheader' import { @@ -61,6 +64,7 @@ export class Blockchain { consensus: Consensus seedGenesisBlock: SerializedBlock config: Config + blockHasher: BlockHasher readonly blockchainDb: BlockchainDB readonly notes: MerkleTree< @@ -142,6 +146,7 @@ export class Blockchain { consensus: Consensus genesis: SerializedBlock config: Config + blockHasher: BlockHasher }) { const logger = options.logger || createRootLogger() @@ -159,6 +164,7 @@ export class Blockchain { this.consensus = options.consensus this.seedGenesisBlock = options.genesis this.config = options.config + this.blockHasher = options.blockHasher this.blockchainDb = new BlockchainDB({ files: options.files, @@ -224,7 +230,7 @@ export class Blockchain { private async load(): Promise { let genesisHeader = await this.getHeaderAtSequence(GENESIS_BLOCK_SEQUENCE) - const seedGenesisBlock = BlockSerde.deserialize(this.seedGenesisBlock, this.strategy) + const seedGenesisBlock = BlockSerde.deserialize(this.seedGenesisBlock, this) if (genesisHeader) { Assert.isTrue( @@ -945,7 +951,7 @@ export class Blockchain { graffiti, } - const header = this.strategy.newBlockHeader(rawHeader, noteSize, BigInt(0)) + const header = this.newBlockHeaderFromRaw(rawHeader, noteSize, BigInt(0)) const block = new Block(header, transactions) if (verifyBlock && !previousBlockHash.equals(GENESIS_BLOCK_PREVIOUS)) { @@ -1473,6 +1479,20 @@ export class Blockchain { const asset = await this.blockchainDb.getAsset(assetId, tx) return asset || null } + + newBlockHeaderFromRaw( + raw: RawBlockHeader, + noteSize?: number | null, + work?: bigint, + ): BlockHeader { + const hash = this.blockHasher.hashHeader(raw) + return new BlockHeader(raw, hash, noteSize, work) + } + + newBlockFromRaw(raw: RawBlock, noteSize?: number | null, work?: bigint): Block { + const header = this.newBlockHeaderFromRaw(raw.header, noteSize, work) + return new Block(header, raw.transactions) + } } export class VerifyError extends Error { diff --git a/ironfish/src/consensus/verifier.test.ts b/ironfish/src/consensus/verifier.test.ts index 4977bb9df1..a832ee7b20 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 = nodeTest.strategy.newBlock({ + const invalidBlock = nodeTest.chain.newBlockFromRaw({ 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 = nodeTest.strategy.newBlock({ + const invalidBlock = nodeTest.chain.newBlockFromRaw({ header: { ...block.header, transactionCommitment: transactionCommitment(reorderedTransactions), @@ -272,7 +272,7 @@ describe('Verifier', () => { }, ) - const invalidMinersBlock = nodeTest.strategy.newBlock({ + const invalidMinersBlock = nodeTest.chain.newBlockFromRaw({ 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 = nodeTest.strategy.newBlock({ + const invalidBlock = nodeTest.chain.newBlockFromRaw({ header: { ...block.header, transactionCommitment: transactionCommitment([]), @@ -316,7 +316,7 @@ describe('Verifier', () => { const invalidTransactions = [...block.transactions, extraTransaction] - const invalidBlock = nodeTest.strategy.newBlock({ + const invalidBlock = nodeTest.chain.newBlockFromRaw({ header: { ...block.header, transactionCommitment: transactionCommitment(invalidTransactions), @@ -639,7 +639,7 @@ describe('Verifier', () => { nodeTest.verifier.enableVerifyTarget = true const block = await useMinerBlockFixture(nodeTest.chain) - const invalidHeader = nodeTest.strategy.newBlockHeader({ + const invalidHeader = nodeTest.chain.newBlockHeaderFromRaw({ ...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 = nodeTest.strategy.newBlockHeader({ + const invalidHeader = nodeTest.chain.newBlockHeaderFromRaw({ ...block.header, sequence: 9999, }) @@ -692,7 +692,7 @@ describe('Verifier', () => { }) it('fails validation when timestamp is too low', async () => { - const invalidHeader = nodeTest.strategy.newBlockHeader({ + const invalidHeader = nodeTest.chain.newBlockHeaderFromRaw({ ...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 = nodeTest.strategy.newBlockHeader({ + const invalidHeader = nodeTest.chain.newBlockHeaderFromRaw({ ...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 = nodeTest.strategy.newBlockHeader({ + const invalidHeader = nodeTest.chain.newBlockHeaderFromRaw({ ...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 = nodeTest.strategy.newBlockHeader({ + const invalidHeader = nodeTest.chain.newBlockHeaderFromRaw({ ...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 = nodeTest.strategy.newBlockHeader({ + const invalidHeader = nodeTest.chain.newBlockHeaderFromRaw({ ...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 = nodeTest.strategy.newBlockHeader({ + const invalidHeader = nodeTest.chain.newBlockHeaderFromRaw({ ...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 = nodeTest.strategy.newBlockHeader({ + const invalidHeader = nodeTest.chain.newBlockHeaderFromRaw({ ...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 = nodeTest.strategy.newBlockHeader({ + const invalidHeader = nodeTest.chain.newBlockHeaderFromRaw({ ...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 = nodeTest.strategy.newBlockHeader({ + const invalidHeader = nodeTest.chain.newBlockHeaderFromRaw({ ...header, timestamp: new Date(prevHeader.timestamp.getTime() + 1 * 1000), }) @@ -895,7 +895,7 @@ describe('Verifier', () => { const newBlock = await useMinerBlockFixture(chain) await expect(chain).toAddBlock(newBlock) - const invalidNewBlock = nodeTest.strategy.newBlock( + const invalidNewBlock = nodeTest.chain.newBlockFromRaw( { header: { ...newBlock.header, diff --git a/ironfish/src/genesis/addGenesisTransaction.ts b/ironfish/src/genesis/addGenesisTransaction.ts index 8cac259ed4..64f03958f7 100644 --- a/ironfish/src/genesis/addGenesisTransaction.ts +++ b/ironfish/src/genesis/addGenesisTransaction.ts @@ -141,7 +141,7 @@ export async function addGenesisTransaction( graffiti: genesisBlock.header.graffiti, } - const newGenesisHeader = node.chain.strategy.newBlockHeader(rawHeader, noteSize) + const newGenesisHeader = node.chain.newBlockHeaderFromRaw(rawHeader, noteSize) genesisBlock.header = newGenesisHeader diff --git a/ironfish/src/genesis/genesis.test.slow.ts b/ironfish/src/genesis/genesis.test.slow.ts index 1b20bb7063..8b1831e3ba 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, nodeTest.strategy) + const deserializedBlock = BlockSerde.deserialize(result, nodeTest.chain) 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, nodeTest.strategy) + const deserializedBlock = BlockSerde.deserialize(result, nodeTest.chain) 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 aedd5fb69b..69c269aab2 100644 --- a/ironfish/src/genesis/makeGenesisBlock.ts +++ b/ironfish/src/genesis/makeGenesisBlock.ts @@ -170,7 +170,7 @@ export async function makeGenesisBlock( GraffitiUtils.fromString('genesis'), ) - const genesisBlock = chain.strategy.newBlock( + const genesisBlock = chain.newBlockFromRaw( { header: { ...block.header, diff --git a/ironfish/src/mining/manager.test.slow.ts b/ironfish/src/mining/manager.test.slow.ts index 9f0ec2aad1..25a7c7fd15 100644 --- a/ironfish/src/mining/manager.test.slow.ts +++ b/ironfish/src/mining/manager.test.slow.ts @@ -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 = nodeTest.strategy.newBlock({ + const blockB1 = nodeTest.chain.newBlockFromRaw({ 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, nodeTest.strategy) - const blockA2 = nodeTest.strategy.newBlock({ + const blockA2Temp = BlockTemplateSerde.deserialize(templateA2, nodeTest.chain) + const blockA2 = nodeTest.chain.newBlockFromRaw({ 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, nodeTest.strategy) + const submittedBlock = BlockTemplateSerde.deserialize(template, nodeTest.chain) 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 3e9b9ef3cd..12cdc6f0cc 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, this.chain.strategy) + const block = BlockTemplateSerde.deserialize(template, this.chain) 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, this.chain.strategy) + const block = BlockTemplateSerde.deserialize(blockTemplate, this.chain) 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 4aaa8f7898..1256a90371 100644 --- a/ironfish/src/network/messages/getBlocks.test.ts +++ b/ironfish/src/network/messages/getBlocks.test.ts @@ -36,7 +36,7 @@ describe('GetBlocksResponse', () => { const message = new GetBlocksResponse( [ new Block( - nodeTest.strategy.newBlockHeader({ + nodeTest.chain.newBlockHeaderFromRaw({ sequence: 2, previousBlockHash: Buffer.alloc(32, 2), noteCommitment: Buffer.alloc(32, 4), @@ -49,7 +49,7 @@ describe('GetBlocksResponse', () => { [transactionA, transactionB], ), new Block( - nodeTest.strategy.newBlockHeader({ + nodeTest.chain.newBlockHeaderFromRaw({ 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 1ab9c0de68..d1c89b2d7b 100644 --- a/ironfish/src/network/messages/getCompactBlock.test.ts +++ b/ironfish/src/network/messages/getCompactBlock.test.ts @@ -33,7 +33,7 @@ describe('GetCompactBlockResponse', () => { const transactionB = await useMinersTxFixture(nodeTest.node, account) const compactBlock: CompactBlock = { - header: nodeTest.strategy.newBlockHeader({ + header: nodeTest.chain.newBlockHeaderFromRaw({ sequence: 2, previousBlockHash: Buffer.alloc(32, 2), noteCommitment: Buffer.alloc(32, 1), diff --git a/ironfish/src/network/peerNetwork.test.ts b/ironfish/src/network/peerNetwork.test.ts index d3592d661c..3dda7949ed 100644 --- a/ironfish/src/network/peerNetwork.test.ts +++ b/ironfish/src/network/peerNetwork.test.ts @@ -763,7 +763,7 @@ describe('PeerNetwork', () => { expect(sendSpy).not.toHaveBeenCalled() } - const invalidHeader = nodeTest.strategy.newBlockHeader(invalidBlock.header) + const invalidHeader = nodeTest.chain.newBlockHeaderFromRaw(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 dea1ccd96d..d1978b0066 100644 --- a/ironfish/src/network/peerNetwork.ts +++ b/ironfish/src/network/peerNetwork.ts @@ -615,7 +615,7 @@ export class PeerNetwork { } const headers = response.headers.map((rawHeader) => - this.chain.strategy.newBlockHeader(rawHeader), + this.chain.newBlockHeaderFromRaw(rawHeader), ) return { headers, time: BenchUtils.end(begin) } @@ -639,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) => this.chain.strategy.newBlock(rawBlock)) + const blocks = response.blocks.map((rawBlock) => this.chain.newBlockFromRaw(rawBlock)) return { blocks, time: BenchUtils.end(begin), isMessageFull } } @@ -789,7 +789,7 @@ export class PeerNetwork { return } - const header = this.chain.strategy.newBlockHeader(compactBlock.header) + const header = this.chain.newBlockHeaderFromRaw(compactBlock.header) // mark the block as received in the block fetcher and decide whether to continue // to validate this compact block or not @@ -1175,7 +1175,7 @@ export class PeerNetwork { return } - const block = this.chain.strategy.newBlock(rawBlock) + const block = this.chain.newBlockFromRaw(rawBlock) if (await this.alreadyHaveBlock(block.header)) { return diff --git a/ironfish/src/node.ts b/ironfish/src/node.ts index 03c0922a8c..2a3a65ffcd 100644 --- a/ironfish/src/node.ts +++ b/ironfish/src/node.ts @@ -5,6 +5,7 @@ import { BoxKeyPair, FishHashContext } from '@ironfish/rust-nodejs' import { v4 as uuid } from 'uuid' import { AssetsVerifier } from './assets' import { Blockchain } from './blockchain' +import { BlockHasher } from './blockHasher' import { Consensus } from './consensus' import { Config, @@ -277,8 +278,13 @@ export class FullNode { fishHashContext = new FishHashContext(isFull) } + const blockHasher = new BlockHasher({ + consensus: consensus, + context: fishHashContext, + }) + strategyClass = strategyClass || Strategy - const strategy = new strategyClass({ workerPool, consensus, fishHashContext }) + const strategy = new strategyClass({ workerPool, consensus, blockHasher }) const chain = new Blockchain({ location: config.chainDatabasePath, @@ -291,6 +297,7 @@ export class FullNode { consensus, genesis: networkDefinition.genesis, config, + blockHasher, }) const feeEstimator = new FeeEstimator({ diff --git a/ironfish/src/primitives/block.test.ts b/ironfish/src/primitives/block.test.ts index 08b3c82316..85b851a41e 100644 --- a/ironfish/src/primitives/block.test.ts +++ b/ironfish/src/primitives/block.test.ts @@ -36,13 +36,13 @@ describe('Block', () => { const serialized = BlockSerde.serialize(block) expect(serialized).toMatchObject({ header: { timestamp: expect.any(Number) } }) - const deserialized = BlockSerde.deserialize(serialized, nodeTest.strategy) + const deserialized = BlockSerde.deserialize(serialized, nodeTest.chain) expect(block.equals(deserialized)).toBe(true) }) it('throws when deserializing invalid block', () => { expect(() => - BlockSerde.deserialize({ bad: 'data' } as unknown as SerializedBlock, nodeTest.strategy), + BlockSerde.deserialize({ bad: 'data' } as unknown as SerializedBlock, nodeTest.chain), ).toThrow('Unable to deserialize') }) @@ -52,10 +52,10 @@ describe('Block', () => { const { block: block1 } = await useBlockWithTx(nodeTest.node, account, account) // Header change - const block2 = BlockSerde.deserialize(BlockSerde.serialize(block1), nodeTest.strategy) + const block2 = BlockSerde.deserialize(BlockSerde.serialize(block1), nodeTest.chain) expect(block1.equals(block2)).toBe(true) - let toCompare = nodeTest.strategy.newBlock({ + let toCompare = nodeTest.chain.newBlockFromRaw({ header: { ...block2.header, randomness: BigInt(400), @@ -64,7 +64,7 @@ describe('Block', () => { }) expect(block1.equals(toCompare)).toBe(false) - toCompare = nodeTest.strategy.newBlock({ + toCompare = nodeTest.chain.newBlockFromRaw({ header: { ...block2.header, sequence: block2.header.sequence + 1, @@ -73,7 +73,7 @@ describe('Block', () => { }) expect(block1.equals(toCompare)).toBe(false) - toCompare = nodeTest.strategy.newBlock({ + toCompare = nodeTest.chain.newBlockFromRaw({ 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), nodeTest.strategy) + const block3 = BlockSerde.deserialize(BlockSerde.serialize(block1), nodeTest.chain) expect(block1.equals(block3)).toBe(true) block3.transactions.pop() expect(block1.equals(block3)).toBe(false) // Transaction equality - const block4 = BlockSerde.deserialize(BlockSerde.serialize(block1), nodeTest.strategy) + const block4 = BlockSerde.deserialize(BlockSerde.serialize(block1), nodeTest.chain) 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 e8f4bb34e7..52d1867284 100644 --- a/ironfish/src/primitives/block.ts +++ b/ironfish/src/primitives/block.ts @@ -4,7 +4,7 @@ import { zip } from 'lodash' import { Assert } from '../assert' -import { Strategy } from '../strategy' +import { Blockchain } from '../blockchain' import { BlockHeader, BlockHeaderSerde, @@ -174,7 +174,7 @@ export class BlockSerde { } } - static deserialize(data: SerializedBlock, strategy: Strategy): Block { + static deserialize(data: SerializedBlock, chain: Blockchain): Block { if ( typeof data === 'object' && data !== null && @@ -182,7 +182,7 @@ export class BlockSerde { 'transactions' in data && Array.isArray(data.transactions) ) { - const header = BlockHeaderSerde.deserialize(data.header, strategy) + const header = BlockHeaderSerde.deserialize(data.header, chain) 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 145c7a0101..9f30cbb728 100644 --- a/ironfish/src/primitives/blockheader.test.ts +++ b/ironfish/src/primitives/blockheader.test.ts @@ -95,7 +95,7 @@ describe('BlockHeader', () => { const nodeTest = createNodeTest() it('checks equal block headers', () => { - const header1 = nodeTest.strategy.newBlockHeader({ + const header1 = nodeTest.chain.newBlockHeaderFromRaw({ sequence: 5, previousBlockHash: Buffer.alloc(32), noteCommitment: Buffer.alloc(32, 'header'), @@ -106,17 +106,17 @@ describe('BlockHeader', () => { graffiti: Buffer.alloc(32), }) - expect(header1.equals(nodeTest.strategy.newBlockHeader({ ...header1 }))).toBe(true) + expect(header1.equals(nodeTest.chain.newBlockHeaderFromRaw({ ...header1 }))).toBe(true) // sequence - expect(header1.equals(nodeTest.strategy.newBlockHeader({ ...header1, sequence: 6 }))).toBe( - false, - ) + expect( + header1.equals(nodeTest.chain.newBlockHeaderFromRaw({ ...header1, sequence: 6 })), + ).toBe(false) // note commitment expect( header1.equals( - nodeTest.strategy.newBlockHeader({ + nodeTest.chain.newBlockHeaderFromRaw({ ...header1, noteCommitment: Buffer.alloc(32, 'not header'), }), @@ -125,25 +125,29 @@ describe('BlockHeader', () => { // target expect( - header1.equals(nodeTest.strategy.newBlockHeader({ ...header1, target: new Target(10) })), + header1.equals( + nodeTest.chain.newBlockHeaderFromRaw({ ...header1, target: new Target(10) }), + ), ).toBe(false) // randomness expect( - header1.equals(nodeTest.strategy.newBlockHeader({ ...header1, randomness: BigInt(19) })), + header1.equals( + nodeTest.chain.newBlockHeaderFromRaw({ ...header1, randomness: BigInt(19) }), + ), ).toBe(false) // timestamp expect( header1.equals( - nodeTest.strategy.newBlockHeader({ ...header1, timestamp: new Date(1000) }), + nodeTest.chain.newBlockHeaderFromRaw({ ...header1, timestamp: new Date(1000) }), ), ).toBe(false) // graffiti expect( header1.equals( - nodeTest.strategy.newBlockHeader({ ...header1, graffiti: Buffer.alloc(32, 'a') }), + nodeTest.chain.newBlockHeaderFromRaw({ ...header1, graffiti: Buffer.alloc(32, 'a') }), ), ).toBe(false) }) @@ -154,7 +158,7 @@ describe('BlockHeaderSerde', () => { const nodeTest = createNodeTest() it('serializes and deserializes a block header', () => { - const header = nodeTest.strategy.newBlockHeader({ + const header = nodeTest.chain.newBlockHeaderFromRaw({ sequence: 5, previousBlockHash: Buffer.alloc(32), noteCommitment: Buffer.alloc(32), @@ -166,12 +170,12 @@ describe('BlockHeaderSerde', () => { }) const serialized = serde.serialize(header) - const deserialized = serde.deserialize(serialized, nodeTest.strategy) + const deserialized = serde.deserialize(serialized, nodeTest.chain) expect(header.equals(deserialized)).toBe(true) }) it('checks block is later than', () => { - const header1 = nodeTest.strategy.newBlockHeader({ + const header1 = nodeTest.chain.newBlockHeaderFromRaw({ sequence: 5, previousBlockHash: Buffer.alloc(32), noteCommitment: Buffer.alloc(32), @@ -182,16 +186,18 @@ describe('BlockHeaderSerde', () => { graffiti: Buffer.alloc(32), }) - expect(isBlockLater(header1, nodeTest.strategy.newBlockHeader({ ...header1 }))).toBe(false) + expect(isBlockLater(header1, nodeTest.chain.newBlockHeaderFromRaw({ ...header1 }))).toBe( + false, + ) expect( isBlockLater( header1, - nodeTest.strategy.newBlockHeader({ ...header1, sequence: header1.sequence - 1 }), + nodeTest.chain.newBlockHeaderFromRaw({ ...header1, sequence: header1.sequence - 1 }), ), ).toBe(true) - const header2 = nodeTest.strategy.newBlockHeader({ + const header2 = nodeTest.chain.newBlockHeaderFromRaw({ ...header1, graffiti: Buffer.alloc(32, 'a'), }) @@ -201,7 +207,7 @@ describe('BlockHeaderSerde', () => { }) it('checks block is heavier than', () => { - const header1 = nodeTest.strategy.newBlockHeader({ + const header1 = nodeTest.chain.newBlockHeaderFromRaw({ sequence: 5, previousBlockHash: Buffer.alloc(32), noteCommitment: Buffer.alloc(32), @@ -213,28 +219,34 @@ describe('BlockHeaderSerde', () => { }) const serialized = serde.serialize(header1) - let header2 = serde.deserialize(serialized, nodeTest.strategy) + let header2 = serde.deserialize(serialized, nodeTest.chain) expect(isBlockHeavier(header1, header2)).toBe(false) header1.work = BigInt(1) header2.work = BigInt(0) expect(isBlockHeavier(header1, header2)).toBe(true) - header2 = nodeTest.strategy.newBlockHeader({ ...header1, sequence: header1.sequence - 1 }) + header2 = nodeTest.chain.newBlockHeaderFromRaw({ + ...header1, + sequence: header1.sequence - 1, + }) header1.work = BigInt(0) header2.work = BigInt(0) expect(isBlockHeavier(header1, header2)).toBe(true) - header2 = nodeTest.strategy.newBlockHeader({ ...header1, target: new Target(200) }) + header2 = nodeTest.chain.newBlockHeaderFromRaw({ ...header1, target: new Target(200) }) header1.work = BigInt(0) header2.work = BigInt(0) expect(isBlockHeavier(header1, header2)).toBe(true) - header2 = nodeTest.strategy.newBlockHeader({ ...header1, target: new Target(200) }) + header2 = nodeTest.chain.newBlockHeaderFromRaw({ ...header1, target: new Target(200) }) header1.work = BigInt(0) header2.work = BigInt(0) - header2 = nodeTest.strategy.newBlockHeader({ ...header1, graffiti: Buffer.alloc(32, 'a') }) + header2 = nodeTest.chain.newBlockHeaderFromRaw({ + ...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 178201ee75..933a225e23 100644 --- a/ironfish/src/primitives/blockheader.ts +++ b/ironfish/src/primitives/blockheader.ts @@ -5,8 +5,8 @@ import { blake3 } from '@napi-rs/blake-hash' import bufio from 'bufio' import { Assert } from '../assert' +import { Blockchain } from '../blockchain' import { BlockHashSerdeInstance, GraffitiSerdeInstance } from '../serde' -import { Strategy } from '../strategy' import { BigIntUtils } from '../utils/bigint' import { NoteEncryptedHash, SerializedNoteEncryptedHash } from './noteEncrypted' import { Target } from './target' @@ -273,8 +273,8 @@ export class BlockHeaderSerde { } } - static deserialize(data: SerializedBlockHeader, strategy: Strategy): BlockHeader { - return strategy.newBlockHeader( + static deserialize(data: SerializedBlockHeader, chain: Blockchain): BlockHeader { + return chain.newBlockHeaderFromRaw( { sequence: Number(data.sequence), previousBlockHash: Buffer.from( diff --git a/ironfish/src/serde/BlockTemplateSerde.ts b/ironfish/src/serde/BlockTemplateSerde.ts index b1142862c4..7a18ce4b45 100644 --- a/ironfish/src/serde/BlockTemplateSerde.ts +++ b/ironfish/src/serde/BlockTemplateSerde.ts @@ -2,11 +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 { Blockchain } from '../blockchain' import { Block, RawBlock } from '../primitives/block' 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 = { @@ -84,9 +84,9 @@ export class BlockTemplateSerde { return RawBlockTemplateSerde.serialize(block, previousBlock) } - static deserialize(blockTemplate: SerializedBlockTemplate, strategy: Strategy): Block { + static deserialize(blockTemplate: SerializedBlockTemplate, chain: Blockchain): Block { const rawBlock = RawBlockTemplateSerde.deserialize(blockTemplate) - return strategy.newBlock(rawBlock) + return chain.newBlockFromRaw(rawBlock) } } diff --git a/ironfish/src/strategy.test.slow.ts b/ironfish/src/strategy.test.slow.ts index 8ef1251796..48db482df3 100644 --- a/ironfish/src/strategy.test.slow.ts +++ b/ironfish/src/strategy.test.slow.ts @@ -11,14 +11,13 @@ import { Transaction as NativeTransaction, TransactionPosted as NativeTransactionPosted, } from '@ironfish/rust-nodejs' -import { blake3 } from '@napi-rs/blake-hash' -import { serializeHeaderBlake3, serializeHeaderFishHash } from './blockHasher' +import { BlockHasher } from './blockHasher' import { Consensus, ConsensusParameters } from './consensus' import { MerkleTree } from './merkletree' import { LeafEncoding } from './merkletree/database/leaves' import { NodeEncoding } from './merkletree/database/nodes' import { NoteHasher } from './merkletree/hasher' -import { Target, Transaction } from './primitives' +import { Transaction } from './primitives' import { Note } from './primitives/note' import { NoteEncrypted, NoteEncryptedHash } from './primitives/noteEncrypted' import { TransactionVersion } from './primitives/transaction' @@ -102,10 +101,12 @@ describe('Demonstrate the Sapling API', () => { spenderKey = generateKey() receiverKey = generateKey() workerPool = new WorkerPool() + const consensus = new Consensus(consensusParameters) + const blockHasher = new BlockHasher({ consensus, context: FISH_HASH_CONTEXT }) strategy = new Strategy({ workerPool, - consensus: new Consensus(consensusParameters), - fishHashContext: FISH_HASH_CONTEXT, + consensus, + blockHasher, }) }) @@ -257,10 +258,12 @@ describe('Demonstrate the Sapling API', () => { } const key = generateKey() + const consensus = new Consensus(modifiedParams) + const blockHasher = new BlockHasher({ consensus, context: FISH_HASH_CONTEXT }) const modifiedStrategy = new Strategy({ workerPool, - consensus: new Consensus(modifiedParams), - fishHashContext: FISH_HASH_CONTEXT, + consensus, + blockHasher, }) const minersFee1 = await modifiedStrategy.createMinersFee( @@ -348,82 +351,4 @@ describe('Demonstrate the Sapling API', () => { expect(await workerPool.verifyTransactions([postedTransaction])).toEqual({ valid: true }) }) }) - - describe('Hashes blocks with correct hashing algorithm', () => { - let modifiedStrategy: Strategy - const modifiedParams = { - ...consensusParameters, - enableFishHash: 10, - } - - beforeAll(() => { - modifiedStrategy = new Strategy({ - workerPool, - consensus: new Consensus(modifiedParams), - fishHashContext: FISH_HASH_CONTEXT, - }) - }) - - const rawHeaderFields = { - previousBlockHash: Buffer.alloc(32), - noteCommitment: Buffer.alloc(32, 'header'), - transactionCommitment: Buffer.alloc(32, 'transactionRoot'), - target: new Target(17), - randomness: BigInt(25), - timestamp: new Date(1598467858637), - graffiti: Buffer.alloc(32), - } - - it('Creates block headers with blake3 before the activation sequence', () => { - const rawHeader = { - ...rawHeaderFields, - sequence: modifiedParams.enableFishHash - 1, - } - const header = modifiedStrategy.newBlockHeader(rawHeader) - - expect(header.hash.equals(blake3(serializeHeaderBlake3(rawHeader)))).toBe(true) - - expect(header.hash.equals(blake3(serializeHeaderFishHash(rawHeader)))).not.toBe(true) - expect( - header.hash.equals(FISH_HASH_CONTEXT.hash(serializeHeaderBlake3(rawHeader))), - ).not.toBe(true) - - expect(header.toRaw()).toEqual(rawHeader) - }) - - it('Creates block headers with FishHash after the activation sequence', () => { - const rawHeader = { - ...rawHeaderFields, - sequence: modifiedParams.enableFishHash, - } - const header = modifiedStrategy.newBlockHeader(rawHeader) - - expect( - header.hash.equals(FISH_HASH_CONTEXT.hash(serializeHeaderFishHash(rawHeader))), - ).toBe(true) - - expect( - header.hash.equals(FISH_HASH_CONTEXT.hash(serializeHeaderBlake3(rawHeader))), - ).not.toBe(true) - - expect(header.hash.equals(blake3(serializeHeaderFishHash(rawHeader)))).not.toBe(true) - - expect(header.toRaw()).toEqual(rawHeader) - }) - - it('Creates block headers with noteSize and work if passed in', () => { - const rawHeader = { - ...rawHeaderFields, - sequence: modifiedParams.enableFishHash - 1, - } - - const header1 = modifiedStrategy.newBlockHeader(rawHeader) - expect(header1.noteSize).toBeNull() - expect(header1.work).toEqual(BigInt(0)) - - const header2 = modifiedStrategy.newBlockHeader(rawHeader, 123, BigInt(456)) - expect(header2.noteSize).toEqual(123) - expect(header2.work).toEqual(BigInt(456)) - }) - }) }) diff --git a/ironfish/src/strategy.test.ts b/ironfish/src/strategy.test.ts index 5140b20d72..163613fc84 100644 --- a/ironfish/src/strategy.test.ts +++ b/ironfish/src/strategy.test.ts @@ -2,6 +2,7 @@ * 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 { BlockHasher } from './blockHasher' import { Consensus, ConsensusParameters } from './consensus' import { Strategy } from './strategy' import { FISH_HASH_CONTEXT } from './testUtilities' @@ -23,10 +24,12 @@ describe('Miners reward', () => { } beforeAll(() => { + const consensus = new Consensus(consensusParameters) + const blockHasher = new BlockHasher({ consensus, context: FISH_HASH_CONTEXT }) strategy = new Strategy({ workerPool: new WorkerPool(), - consensus: new Consensus(consensusParameters), - fishHashContext: FISH_HASH_CONTEXT, + consensus, + blockHasher, }) }) diff --git a/ironfish/src/strategy.ts b/ironfish/src/strategy.ts index 783c6e2073..673bab397d 100644 --- a/ironfish/src/strategy.ts +++ b/ironfish/src/strategy.ts @@ -1,11 +1,8 @@ /* 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 { FishHashContext } from '@ironfish/rust-nodejs' import { BlockHasher } from './blockHasher' import { Consensus } from './consensus' -import { Block, RawBlock } from './primitives/block' -import { BlockHeader, RawBlockHeader } from './primitives/blockheader' import { Transaction } from './primitives/transaction' import { MathUtils } from './utils' import { WorkerPool } from './workerPool' @@ -23,15 +20,12 @@ export class Strategy { constructor(options: { workerPool: WorkerPool consensus: Consensus - fishHashContext?: FishHashContext + blockHasher: BlockHasher }) { this.miningRewardCachedByYear = new Map() this.workerPool = options.workerPool this.consensus = options.consensus - this.blockHasher = new BlockHasher({ - consensus: this.consensus, - context: options.fishHashContext, - }) + this.blockHasher = options.blockHasher } /** @@ -104,14 +98,4 @@ export class Strategy { return this.workerPool.createMinersFee(minerSpendKey, amount, '', transactionVersion) } - - newBlockHeader(raw: RawBlockHeader, noteSize?: number | null, work?: bigint): BlockHeader { - const hash = this.blockHasher.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) - } } diff --git a/ironfish/src/testUtilities/fixtures/blocks.ts b/ironfish/src/testUtilities/fixtures/blocks.ts index 517d288e16..2f6bd5e17c 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, chain.strategy) + return BlockSerde.deserialize(serialized, chain) }, }) } diff --git a/ironfish/src/utils/blockutil.test.ts b/ironfish/src/utils/blockutil.test.ts index c491b90056..6feacdd5de 100644 --- a/ironfish/src/utils/blockutil.test.ts +++ b/ironfish/src/utils/blockutil.test.ts @@ -68,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 = nodeTest.strategy.newBlockHeader({ + nodeTest.chain.latest = nodeTest.chain.newBlockHeaderFromRaw({ ...nodeTest.chain.latest, sequence: 10000, }) @@ -79,7 +79,7 @@ describe('BlockchainUtils', () => { }) it('{ start: null, stop: 6000 } returns 1 6000', () => { - nodeTest.chain.latest = nodeTest.strategy.newBlockHeader({ + nodeTest.chain.latest = nodeTest.chain.newBlockHeaderFromRaw({ ...nodeTest.chain.latest, sequence: 10000, }) @@ -90,7 +90,7 @@ describe('BlockchainUtils', () => { }) it('{ start: 6000, stop: null } returns 6000 10000', () => { - nodeTest.chain.latest = nodeTest.strategy.newBlockHeader({ + nodeTest.chain.latest = nodeTest.chain.newBlockHeaderFromRaw({ ...nodeTest.chain.latest, sequence: 10000, }) From d0a3882ae56c621cf43c254f914a8bb2e5aa1440 Mon Sep 17 00:00:00 2001 From: Rahul Patni Date: Tue, 30 Jan 2024 09:37:14 -0800 Subject: [PATCH 09/27] Changes frost test to use participantB address (#4629) * Changes frost test to use participantB address The goal is to make it explicit in the test that the participants all have the same address. * adding comment --- ironfish/src/wallet/wallet.test.slow.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ironfish/src/wallet/wallet.test.slow.ts b/ironfish/src/wallet/wallet.test.slow.ts index 5a71a543ec..d4bb0f5f46 100644 --- a/ironfish/src/wallet/wallet.test.slow.ts +++ b/ironfish/src/wallet/wallet.test.slow.ts @@ -1230,11 +1230,14 @@ describe('Wallet', () => { await expect(node.chain).toAddBlock(block) await node.wallet.updateHead() + // we are using participant B and sending the transaction below from participant A + // to make it extremely obvious that the participants in the multisig account control + // the same account. const transaction = await node.wallet.send({ account: miner, outputs: [ { - publicAddress: participantA.publicAddress, + publicAddress: participantB.publicAddress, amount: BigInt(2), memo: '', assetId: Asset.nativeId(), From 9a633d849f7e973b9e707500a7ad82d5b1447b03 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 30 Jan 2024 10:39:02 -0800 Subject: [PATCH 10/27] adds napi binding for UnsignedTransaction.sign (#4635) exposing 'sign' on the TypeScript side will be useful in the future for signing transactions with pre-computed proofs, and it will be useful now for testing --- ironfish-rust-nodejs/index.d.ts | 1 + ironfish-rust-nodejs/src/structs/transaction.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index 389b745bdf..784e182728 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -247,6 +247,7 @@ export class UnsignedTransaction { serialize(): Buffer publicKeyRandomness(): string signingPackage(nativeCommitments: Record): string + sign(spenderHexKey: string): Buffer signFrost(publicKeyPackageStr: string, signingPackageStr: string, signatureSharesMap: Record): Buffer } export class FoundBlockResult { diff --git a/ironfish-rust-nodejs/src/structs/transaction.rs b/ironfish-rust-nodejs/src/structs/transaction.rs index fbf57f5975..c8788da645 100644 --- a/ironfish-rust-nodejs/src/structs/transaction.rs +++ b/ironfish-rust-nodejs/src/structs/transaction.rs @@ -444,6 +444,18 @@ impl NativeUnsignedTransaction { )) } + #[napi] + pub fn sign(&mut self, spender_hex_key: String) -> Result { + let spender_key = SaplingKey::from_hex(&spender_hex_key).map_err(to_napi_err)?; + + let posted_transaction = self.transaction.sign(&spender_key).map_err(to_napi_err)?; + + let mut vec: Vec = vec![]; + posted_transaction.write(&mut vec).map_err(to_napi_err)?; + + Ok(Buffer::from(vec)) + } + #[napi] pub fn sign_frost( &mut self, From d233c15ee4058786111ba8bd0f64aa7e0674d7df Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Tue, 30 Jan 2024 11:26:45 -0800 Subject: [PATCH 11/27] Use FishHash in rust miner (#4588) --- Cargo.lock | 1 + ironfish-rust-nodejs/index.d.ts | 4 +- ironfish-rust-nodejs/src/lib.rs | 20 ++++++- ironfish-rust/Cargo.toml | 1 + ironfish-rust/src/mining/mine.rs | 77 +++++++++++++++++++++----- ironfish-rust/src/mining/thread.rs | 74 +++++++++++++++++++------ ironfish-rust/src/mining/threadpool.rs | 32 +++++++++-- ironfish/src/mining/poolMiner.ts | 4 +- ironfish/src/mining/soloMiner.ts | 4 +- 9 files changed, 175 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5742b51f2..8d7d857266 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1524,6 +1524,7 @@ dependencies = [ "chacha20poly1305 0.9.1", "crypto_box", "ff 0.12.1", + "fish_hash", "group 0.12.1", "hex", "hex-literal 0.4.1", diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index 784e182728..f7e04a99fc 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -256,8 +256,8 @@ export class FoundBlockResult { constructor(randomness: string, miningRequestId: number) } export class ThreadPoolHandler { - constructor(threadCount: number, batchSize: number, pauseOnSuccess: boolean) - newWork(headerBytes: Buffer, target: Buffer, miningRequestId: number): void + constructor(threadCount: number, batchSize: number, pauseOnSuccess: boolean, useFishHash: boolean, fishHashFullContext: boolean) + newWork(headerBytes: Buffer, target: Buffer, miningRequestId: number, fishHash: boolean): void stop(): void pause(): void getFoundBlock(): FoundBlockResult | null diff --git a/ironfish-rust-nodejs/src/lib.rs b/ironfish-rust-nodejs/src/lib.rs index 6109d5c1ad..7d910a2248 100644 --- a/ironfish-rust-nodejs/src/lib.rs +++ b/ironfish-rust-nodejs/src/lib.rs @@ -127,20 +127,34 @@ pub struct ThreadPoolHandler { #[napi] impl ThreadPoolHandler { #[napi(constructor)] - pub fn new(thread_count: u32, batch_size: u32, pause_on_success: bool) -> Self { + pub fn new( + thread_count: u32, + batch_size: u32, + pause_on_success: bool, + use_fish_hash: bool, + fish_hash_full_context: bool, + ) -> Self { ThreadPoolHandler { threadpool: mining::threadpool::ThreadPool::new( thread_count as usize, batch_size, pause_on_success, + use_fish_hash, + fish_hash_full_context, ), } } #[napi] - pub fn new_work(&mut self, header_bytes: Buffer, target: Buffer, mining_request_id: u32) { + pub fn new_work( + &mut self, + header_bytes: Buffer, + target: Buffer, + mining_request_id: u32, + fish_hash: bool, + ) { self.threadpool - .new_work(&header_bytes, &target, mining_request_id) + .new_work(&header_bytes, &target, mining_request_id, fish_hash) } #[napi] diff --git a/ironfish-rust/Cargo.toml b/ironfish-rust/Cargo.toml index 201464ae0b..c57aea243e 100644 --- a/ironfish-rust/Cargo.toml +++ b/ironfish-rust/Cargo.toml @@ -43,6 +43,7 @@ crypto_box = { version = "0.8", features = ["std"] } ff = "0.12.0" group = "0.12.0" ironfish-frost = { git = "https://github.com/iron-fish/ironfish-frost.git", branch = "main" } +fish_hash = "0.1.0" ironfish_zkp = { version = "0.2.0", path = "../ironfish-zkp" } jubjub = { git = "https://github.com/iron-fish/jubjub.git", branch = "blstrs" } lazy_static = "1.4.0" diff --git a/ironfish-rust/src/mining/mine.rs b/ironfish-rust/src/mining/mine.rs index a16920862d..4b7a308ecf 100644 --- a/ironfish-rust/src/mining/mine.rs +++ b/ironfish-rust/src/mining/mine.rs @@ -2,6 +2,7 @@ * 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/. */ use byteorder::{BigEndian, WriteBytesExt}; +use fish_hash::Context; /// returns true if a <= b when treating both as 32 byte big endian numbers. pub(crate) fn bytes_lte(a: &[u8], b: &[u8]) -> bool { @@ -21,7 +22,7 @@ fn randomize_header(i: u64, mut header_bytes: &mut [u8]) { header_bytes.write_u64::(i).unwrap(); } -pub(crate) fn mine_batch( +pub(crate) fn mine_batch_blake3( header_bytes: &mut [u8], target: &[u8], start: u64, @@ -40,13 +41,40 @@ pub(crate) fn mine_batch( None } +pub(crate) fn mine_batch_fish_hash( + context: &mut Context, + header_bytes: &mut [u8], + target: &[u8], + start: u64, + step_size: usize, + batch_size: u64, +) -> Option { + let end = start + batch_size; + for i in (start..=end).step_by(step_size) { + header_bytes[172..].copy_from_slice(&i.to_be_bytes()); + + let mut hash = [0u8; 32]; + { + fish_hash::hash(&mut hash, context, header_bytes); + } + + if bytes_lte(&hash, target) { + return Some(i); + } + } + + None +} + #[cfg(test)] mod test { use std::io::Cursor; use byteorder::{BigEndian, ReadBytesExt}; - use super::{bytes_lte, mine_batch}; + use crate::mining::mine::mine_batch_fish_hash; + + use super::{bytes_lte, mine_batch_blake3}; #[test] fn test_mine_batch_no_match() { @@ -56,7 +84,7 @@ mod test { let start = 42; let step_size = 1; - let result = mine_batch(header_bytes, target, start, step_size, batch_size); + let result = mine_batch_blake3(header_bytes, target, start, step_size, batch_size); assert!(result.is_none()) } @@ -75,12 +103,35 @@ mod test { 67, 145, 116, 198, 241, 183, 88, 140, 172, 79, 139, 210, 162, ]; - let result = mine_batch(header_bytes, target, start, step_size, batch_size); + let result = mine_batch_blake3(header_bytes, target, start, step_size, batch_size); assert!(result.is_some()); assert_eq!(result.unwrap(), 43); } + #[test] + fn test_mine_batch_match_fish_hash() { + let header_bytes = &mut [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].repeat(18); + let batch_size = 3; + let start = 43; + let step_size = 1; + + let context = &mut fish_hash::Context::new(false); + + // Hardcoded target value derived from a randomness of 45, which is lower than 42 + // This allows us to test the looping and target comparison a little better + let target: &[u8; 32] = &[ + 59, 125, 43, 4, 254, 19, 32, 88, 203, 188, 220, 43, 193, 139, 194, 164, 61, 99, 44, 90, + 151, 122, 236, 65, 253, 171, 148, 82, 130, 54, 122, 195, + ]; + + let result = + mine_batch_fish_hash(context, header_bytes, target, start, step_size, batch_size); + + assert!(result.is_some()); + assert_eq!(result.unwrap(), 45); + } + #[test] fn test_mine_batch_step_size() { let header_bytes_base = &mut (0..128).collect::>(); @@ -96,7 +147,7 @@ mod test { // Uses i values: 0, 3, 6, 9 let header_bytes = &mut header_bytes_base.clone(); - let _ = mine_batch(header_bytes, target, start, step_size, batch_size); + let _ = mine_batch_blake3(header_bytes, target, start, step_size, batch_size); let mut cursor = Cursor::new(header_bytes); let end = cursor.read_u64::().unwrap(); @@ -104,7 +155,7 @@ mod test { // Uses i values: 1, 4, 7, 10 let header_bytes = &mut header_bytes_base.clone(); - let _ = mine_batch(header_bytes, target, start + 1, step_size, batch_size); + let _ = mine_batch_blake3(header_bytes, target, start + 1, step_size, batch_size); let mut cursor = Cursor::new(header_bytes); let end = cursor.read_u64::().unwrap(); @@ -112,7 +163,7 @@ mod test { // Uses i values: 2, 5, 8, 11 let header_bytes = &mut header_bytes_base.clone(); - let _ = mine_batch(header_bytes, target, start + 2, step_size, batch_size); + let _ = mine_batch_blake3(header_bytes, target, start + 2, step_size, batch_size); let mut cursor = Cursor::new(header_bytes); let end = cursor.read_u64::().unwrap(); @@ -125,7 +176,7 @@ mod test { // Uses i values: 12, 15, 18, 21 let header_bytes = &mut header_bytes_base.clone(); - let _ = mine_batch(header_bytes, target, start, step_size, batch_size); + let _ = mine_batch_blake3(header_bytes, target, start, step_size, batch_size); let mut cursor = Cursor::new(header_bytes); let end = cursor.read_u64::().unwrap(); @@ -133,7 +184,7 @@ mod test { // Uses i values: 13, 16, 19, 22 let header_bytes = &mut header_bytes_base.clone(); - let _ = mine_batch(header_bytes, target, start + 1, step_size, batch_size); + let _ = mine_batch_blake3(header_bytes, target, start + 1, step_size, batch_size); let mut cursor = Cursor::new(header_bytes); let end = cursor.read_u64::().unwrap(); @@ -141,7 +192,7 @@ mod test { // Uses i values: 14, 17, 20, 23 let header_bytes = &mut header_bytes_base.clone(); - let _ = mine_batch(header_bytes, target, start + 2, step_size, batch_size); + let _ = mine_batch_blake3(header_bytes, target, start + 2, step_size, batch_size); let mut cursor = Cursor::new(header_bytes); let end = cursor.read_u64::().unwrap(); @@ -154,7 +205,7 @@ mod test { // Uses i values: 24, 27, 30, 33 let header_bytes = &mut header_bytes_base.clone(); - let _ = mine_batch(header_bytes, target, start, step_size, batch_size); + let _ = mine_batch_blake3(header_bytes, target, start, step_size, batch_size); let mut cursor = Cursor::new(header_bytes); let end = cursor.read_u64::().unwrap(); @@ -162,7 +213,7 @@ mod test { // Uses i values: 25, 28, 31, 34 let header_bytes = &mut header_bytes_base.clone(); - let _ = mine_batch(header_bytes, target, start + 1, step_size, batch_size); + let _ = mine_batch_blake3(header_bytes, target, start + 1, step_size, batch_size); let mut cursor = Cursor::new(header_bytes); let end = cursor.read_u64::().unwrap(); @@ -170,7 +221,7 @@ mod test { // Uses i values: 26, 29, 32, 35 let header_bytes = &mut header_bytes_base.clone(); - let _ = mine_batch(header_bytes, target, start + 2, step_size, batch_size); + let _ = mine_batch_blake3(header_bytes, target, start + 2, step_size, batch_size); let mut cursor = Cursor::new(header_bytes); let end = cursor.read_u64::().unwrap(); diff --git a/ironfish-rust/src/mining/thread.rs b/ironfish-rust/src/mining/thread.rs index 104efc56b4..3dd6913823 100644 --- a/ironfish-rust/src/mining/thread.rs +++ b/ironfish-rust/src/mining/thread.rs @@ -8,6 +8,7 @@ use std::{ }; use super::mine; +use fish_hash::Context; #[derive(Debug)] pub(crate) enum Command { @@ -15,14 +16,21 @@ pub(crate) enum Command { Vec, // header bytes Vec, // target u32, // mining request id + bool, // use fish hash ), Stop, Pause, } +pub struct FishHashOptions { + pub enabled: bool, + pub full_context: bool, +} + pub(crate) struct Thread { command_channel: Sender, } + impl Thread { pub(crate) fn new( id: u64, @@ -31,20 +39,30 @@ impl Thread { pool_size: usize, batch_size: u32, pause_on_success: bool, + fish_hash_options: FishHashOptions, ) -> Self { let (work_sender, work_receiver) = mpsc::channel::(); thread::Builder::new() .name(id.to_string()) .spawn(move || { + let mut fish_hash_context = if fish_hash_options.enabled { + Some(fish_hash::Context::new(fish_hash_options.full_context)) + } else { + None + }; + process_commands( work_receiver, block_found_channel, hash_rate_channel, - id, - pool_size, - batch_size as u64, + NonceOptions { + start: id, + step_size: pool_size, + default_batch_size: batch_size as u64, + }, pause_on_success, + &mut fish_hash_context, ) }) .unwrap(); @@ -59,9 +77,14 @@ impl Thread { header_bytes: Vec, target: Vec, mining_request_id: u32, + use_fish_hash: bool, ) -> Result<(), SendError> { - self.command_channel - .send(Command::NewWork(header_bytes, target, mining_request_id)) + self.command_channel.send(Command::NewWork( + header_bytes, + target, + mining_request_id, + use_fish_hash, + )) } pub(crate) fn pause(&self) -> Result<(), SendError> { @@ -73,15 +96,23 @@ impl Thread { } } +struct NonceOptions { + start: u64, + step_size: usize, + default_batch_size: u64, +} + fn process_commands( work_receiver: Receiver, block_found_channel: Sender<(u64, u32)>, hash_rate_channel: Sender, - start: u64, - step_size: usize, - default_batch_size: u64, + nonce_options: NonceOptions, pause_on_success: bool, + fish_hash_context: &mut Option, ) { + let start = nonce_options.start; + let step_size = nonce_options.step_size; + let default_batch_size = nonce_options.default_batch_size; let mut commands: VecDeque = VecDeque::new(); loop { // If there is no pending work, wait for work with a blocking call @@ -92,7 +123,7 @@ fn process_commands( let command = commands.pop_front().unwrap(); match command { - Command::NewWork(mut header_bytes, target, mining_request_id) => { + Command::NewWork(mut header_bytes, target, mining_request_id, use_fish_hash) => { let mut batch_start = start; loop { let remaining_search_space = u64::MAX - batch_start; @@ -101,13 +132,24 @@ fn process_commands( } else { remaining_search_space }; - let match_found = mine::mine_batch( - &mut header_bytes, - &target, - batch_start, - step_size, - batch_size, - ); + + let match_found = match use_fish_hash { + false => mine::mine_batch_blake3( + &mut header_bytes, + &target, + batch_start, + step_size, + batch_size, + ), + true => mine::mine_batch_fish_hash( + fish_hash_context.as_mut().unwrap(), + &mut header_bytes, + &target, + batch_start, + step_size, + batch_size, + ), + }; // Submit amount of work done let work_done = match match_found { diff --git a/ironfish-rust/src/mining/threadpool.rs b/ironfish-rust/src/mining/threadpool.rs index e2540a5d38..d3e5bbd67a 100644 --- a/ironfish-rust/src/mining/threadpool.rs +++ b/ironfish-rust/src/mining/threadpool.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::sync::mpsc::{self, Receiver}; -use super::thread::Thread; +use super::thread::{FishHashOptions, Thread}; pub struct ThreadPool { threads: Vec, @@ -11,14 +11,26 @@ pub struct ThreadPool { hash_rate_receiver: Receiver, mining_request_id: u32, } + impl ThreadPool { - pub fn new(thread_count: usize, batch_size: u32, pause_on_success: bool) -> Self { + pub fn new( + thread_count: usize, + batch_size: u32, + pause_on_success: bool, + use_fish_hash: bool, + fish_hash_full_context: bool, + ) -> Self { let (block_found_channel, block_found_receiver) = mpsc::channel::<(u64, u32)>(); let (hash_rate_channel, hash_rate_receiver) = mpsc::channel::(); let mut threads = Vec::with_capacity(thread_count); for id in 0..thread_count { + let fish_hash_options = FishHashOptions { + enabled: use_fish_hash, + full_context: fish_hash_full_context, + }; + threads.push(Thread::new( id as u64, block_found_channel.clone(), @@ -26,6 +38,7 @@ impl ThreadPool { thread_count, batch_size, pause_on_success, + fish_hash_options, )); } @@ -37,12 +50,23 @@ impl ThreadPool { } } - pub fn new_work(&mut self, header_bytes: &[u8], target: &[u8], mining_request_id: u32) { + pub fn new_work( + &mut self, + header_bytes: &[u8], + target: &[u8], + mining_request_id: u32, + fish_hash: bool, + ) { self.mining_request_id = mining_request_id; for thread in self.threads.iter() { thread - .new_work(header_bytes.to_vec(), target.to_vec(), mining_request_id) + .new_work( + header_bytes.to_vec(), + target.to_vec(), + mining_request_id, + fish_hash, + ) .unwrap(); } } diff --git a/ironfish/src/mining/poolMiner.ts b/ironfish/src/mining/poolMiner.ts index 4b00c06d3d..5cca5b8f3e 100644 --- a/ironfish/src/mining/poolMiner.ts +++ b/ironfish/src/mining/poolMiner.ts @@ -47,7 +47,7 @@ export class MiningPoolMiner { } const threadCount = options.threadCount ?? 1 - this.threadPool = new ThreadPoolHandler(threadCount, options.batchSize, false) + this.threadPool = new ThreadPoolHandler(threadCount, options.batchSize, false, false, false) this.stratum = options.stratum this.stratum.onConnected.on(() => this.stratum.subscribe(this.publicAddress, this.name)) @@ -122,7 +122,7 @@ export class MiningPoolMiner { headerBytes.set(this.graffiti, MINEABLE_BLOCK_HEADER_GRAFFITI_OFFSET) this.waiting = false - this.threadPool.newWork(headerBytes, this.target, miningRequestId) + this.threadPool.newWork(headerBytes, this.target, miningRequestId, false) } waitForWork(): void { diff --git a/ironfish/src/mining/soloMiner.ts b/ironfish/src/mining/soloMiner.ts index 3622a6688f..9c5ccd9177 100644 --- a/ironfish/src/mining/soloMiner.ts +++ b/ironfish/src/mining/soloMiner.ts @@ -58,7 +58,7 @@ export class MiningSoloMiner { this.graffiti = options.graffiti const threadCount = options.threadCount ?? 1 - this.threadPool = new ThreadPoolHandler(threadCount, options.batchSize, true) + this.threadPool = new ThreadPoolHandler(threadCount, options.batchSize, true, false, false) this.miningRequestId = 0 this.nextMiningRequestId = 0 @@ -135,7 +135,7 @@ export class MiningSoloMiner { headerBytes.set(this.graffiti, MINEABLE_BLOCK_HEADER_GRAFFITI_OFFSET) this.waiting = false - this.threadPool.newWork(headerBytes, this.target, miningRequestId) + this.threadPool.newWork(headerBytes, this.target, miningRequestId, false) } waitForWork(): void { From 30e624360f1310be7aef3b7c7dff3d90cf520283 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Tue, 30 Jan 2024 12:05:21 -0800 Subject: [PATCH 12/27] Move wallet test to wallet.test.ts (#4638) This wallet test for createTransaction() was incorrectly in transaction.test.slow. It was testing an error as a result of calling Wallet.createTransaction(). --- .../transaction.test.slow.ts.fixture | 64 +---------- .../src/primitives/transaction.test.slow.ts | 34 ------ .../__fixtures__/wallet.test.ts.fixture | 105 ++++++++++++++++++ ironfish/src/wallet/wallet.test.ts | 35 ++++++ 4 files changed, 141 insertions(+), 97 deletions(-) diff --git a/ironfish/src/primitives/__fixtures__/transaction.test.slow.ts.fixture b/ironfish/src/primitives/__fixtures__/transaction.test.slow.ts.fixture index bc33329703..b531759936 100644 --- a/ironfish/src/primitives/__fixtures__/transaction.test.slow.ts.fixture +++ b/ironfish/src/primitives/__fixtures__/transaction.test.slow.ts.fixture @@ -37,68 +37,6 @@ } } ], - "Transaction throw error if account is not fully synced when creating transaction": [ - { - "version": 2, - "id": "d283dbe7-a71c-4924-a565-24cf056a6b24", - "name": "testA", - "spendingKey": "84596b1a609345a27a5f9a3146157361e7fa419d77a6126460327eda18aafc2a", - "viewKey": "f2b58c13a6d8e27de575a37f04dcae3ed612d129b9e139e8630bc276c099e3e147be6b0e67742864ed1481fdd0792acb45334351fbac2129c0522fd1092fa60d", - "incomingViewKey": "c1c7d58affbea89aea6cd493788e75c2b0c4722ee9514345c39508f3450d1e03", - "outgoingViewKey": "79c00a26cbdbac6cf1eb3348830f1e868dc08700ae1f9454909e9b097a26e9c6", - "publicAddress": "11de04f3699b77a994045aaf26fcf1c1e0402a27d5f02bf44b81b683226cc7f0", - "createdAt": { - "hash": { - "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 - } - }, - { - "version": 2, - "id": "51c4cff5-d48c-48d2-bfbf-46aa8e7f4346", - "name": "testB", - "spendingKey": "92d415945ed382e16dc67bcf40c15a60e6b614dde41ad3737ddcc52c92d9b263", - "viewKey": "fb92691173d3c88bbb1e41bfd1cd341b5668b6b492f783f9948cc02c62174e3b3fcec997366022c2698bb7d82b020b999aa14702858bf819d99af955b1c66bb7", - "incomingViewKey": "e55f21def38f409991d28480b050c43e6ac59cb1c63e6e216517521f55ed9e05", - "outgoingViewKey": "138224cf5bdc50eef098cff1b23ea1d3bf1fb542e0717c4a583f3f8dd1ae9087", - "publicAddress": "fa28629adfbf0a24ea976859a4abb2d8be178776d716422cb491274b54349dd8", - "createdAt": { - "hash": { - "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 - } - }, - { - "header": { - "sequence": 2, - "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", - "noteCommitment": { - "type": "Buffer", - "data": "base64:XMcV9o/QRLNpCLaiKyGwEKmGMNeDdroq+y+h9jD+RlM=" - }, - "transactionCommitment": { - "type": "Buffer", - "data": "base64:neA9YMRbFYjfrzgJ3nubn9QoyYhl6q4q1lEtI7yGVG8=" - }, - "target": "9282972777491357380673661573939192202192629606981189395159182914949423", - "randomness": "0", - "timestamp": 1695140654832, - "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", - "noteSize": 4, - "work": "0" - }, - "transactions": [ - { - "type": "Buffer", - "data": "base64:AgAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAUFzRRKUUw8Vbc1ZusHhJrYDmEmGXE0NVlMfxjv4jmpCid7pHJuJymNR8DmAh0mytk21WGoVJU0UWdUcfkNmGJ2bb2qRGsKJ3AWtVqoMR14ugxE6WgucauybLvRvmIWTXXc40Xq9S67C/UeCkpqsgoTkfib6b0P/4sRUWc5UVJtQMPiMzZJGsjP1WVvEKLzil8xX0l0j2CeyNh9BOmyJNYpfMQEDWPn4e3nK6WKEXtvi1FvT/k/PP9sIs/LDt1up4wFSovbGyHyJN+ER6AoF3H1Lwx2XX33i30dMOotVYqg7JPXOzbjK6ycd21cY8v1AVAHDXYHghdHr4wvN0y3X0OexPSKhs3ZzC5H4115U+2CLxYEpzyChcdZm0D1aDxsNuX7P/14q5LNih4/rmxDNZi88GXgrszsIAW6/23TTFlFt9BhQVckU1+R/BpGSzfY13UMj5WHiPGZ7cPADViqcF/1Nb8nEe0ONik6GzoPBFFmpLAtTO6POA6x5T+eNo8Fv4aCZWpGcaMET0zzvySKErXpdN/WtOJufuIe5K3rOYAvY/N6xA6nz0Tb3XTUfrmayd7XfDKwgkYf8vzXlStpJfTU+oq+xLwWopUHWvPCe64u7uyts+Y5jsh0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwvEQbyBC7GVPG1CZLVctTeKm7gfThTdQN9Bly+pdx/uk6bGoAlBcRTCMCGubOr4cxSV6BkyPOHtROKKEeR/m0Bw==" - } - ] - } - ], "Transaction check if a transaction is not a miners fee": [ { "version": 2, @@ -244,4 +182,4 @@ "data": "base64:AgIAAAAAAAAAAwAAAAAAAAABAAAAAAAAAAEAAAAAAAAABQAAAAAAAAAKAAAAC3mTTnexuv8GM+i6b/3C1et6tqEStAg/74Ul4by+WMGiaipmlaGCoAHaytSiGvg8ogpXZabo8YaAmfvp8satdeP3IoZ8XUmpPMk4JutaR7qX4teywYW1mVHaX0xKM7EcROEgU3Kdq3zt/S9CqkeFR8QuDxGKaR0tGtZdjVyKNiILAQ8pVVRq+Z1paBIHmxGqo1k4zFhNd1bPOBMQyhwFb+t41ZQMhcmJkMD5PlxDXNGVqG7pey/BCqoWiJEqG33daV9CrXXY69dRAdra0FA8RIELu/+6E7HAlEkE888G0yXhYes2LeOV7Kt394Bk42Sft8ZlXVpJeTR9acckMNDNhLDlSwydmg21DUarHeHJhAWKe1TZnq4x7ZCibK4Lmj8sBgAAABVOGuOBz7eXK7KdmP/1cnboY+jNnjiRlx8ID/wdUAW+5ahnzy7LdFy+XQRjoHvT0eMbhzw7fb2bRw/6M58Q/UqnZA31lbukS4IGCjC/TYSNl1Z7MYatC1ni5cCbxVVgCZE9yK0YQ4ZqcbjHCUkgm+wjQNET5fo8y8YJ2ZCzRDjNNeX2yE5bFb5H+CqAI/2v9LTdKXGCWjFtEp5P0XVVHgjzoXP39PFCfiOYkJo01njDeVctTYN55UV8GJUNMn4H3xUj19ItL5jNPMZdT85JUsHrj7Ui97A1i5qRhLMk/UcaYPiq5v9JiAsNe842dvLSfrIF7ZBrxkiu33s3z/sn1BKjDYvEFUDHQjz8PMtduV4MYtJzruY4DoG2lav6+BBRI/2sctzGxjHz2I5jwc2N++86s3K4PKgpc2fup4zsCdfNsOVLDJ2aDbUNRqsd4cmEBYp7VNmerjHtkKJsrguaPywGAAAAg2av4pHGhgbpa4IxlbQS6hrdYyiJrQru6/hpLrBIcH10gbCYDAqv4lN/usABPmwf8KXgOd2u1RY5NQ5QrCiVtqbPV0GscM828xXhWX9pDs8qgeEG82iCmZ/jk83GZ/8FlxJ+4u++UbgFCgj2+w7+jNx77mRZvkK/n7CIF7Czp3NJ19APwYOXVYIcItJkePbzqNqXsU/yhGkU+r7+3iLJFWcHpcYZbUmjmMO1E8mrmw5vS9jlE0SwVWq8sEw0vKKADmHdIgiQmaXAy+MFXSFZsscGN8lno8fj+uK2xaCYqZfTO5M29FR/3J7oSq8IfH3Kl87EmRccTA4l3wY++v/1a63/oUUWV8/yFW7jxNeIL2yzUJZoM2wJ+FB5Xyqr3zi9PCqgr7SftG49JWoSuqyrZqt6FIbgINXjQ7qSNbjQkO7uU/DIcnl5EAPCuSDijm9IO/IVXmKV6T8E4e+vxlWKDa0AFIS3eGodl5YNcsblK3gBlOx1cu2zVvVcSlFluvfsuwOmw7GRKw2iSZx5qBYVQut2X0CzNTlVHbY83kTwDgWs0prCFsePkchswlHOAn0nTsGFGc8XIE5smGgnpgjRhX5X+IpaeFjH2vDs7KYM+JL+s82fArxKkvvU5mI2AQPd5aYH/Zt6262N9f+DXcdv5VRB4+X5pcfXwfgsH/L4L/rRaRFCZKF4Hnc55LKgYCAtLDEvKpfkMHo1PxbPmryjHCHQe29FwlqFO44AuABZSJVw+fgbMPa+q0YCiudGADixuwIX/VWcGcIyPNc9HI3L7TD2QesoDcX83UPNUK0GyiGkdy/xCqrpn6uwUhtuEtgfSIq2mnu6BN7VO+4jFq7Z7cD2gSIBV3Vpj5Gk7DH8T2AzezFJv6svcoc7qZ94fDwX5AvLoONqeggAZcStIOc/lWH2LwRm4W/nGYh8LrxH9gwCJ4ivc+qMbAn6C1j8QhSRHmx7PM3MTfQlvkxZAUiHO54FgKpmnzIP/ElHhpcX8zsZEG3mIjxmZ5aTE2wfZcZUGbRekNVCrSxy5TXzWyKIi5nUMNMj2pF35SHDyxsK4/4/lB4Zfk0/P/TLfqCuu2mCcSGZex1YtcksGe+XbLWkSW+E0qxblh3odOgWMUFy5VFFIh8kkkY84S+ky1gmtKDxtK5ogLSEEwiyfcr/M5LIfHHWQEZSyPJyBkzJ/TsyBfhRGAudtATkqwr3BVodLztdCuxj3nPxWqvEKAFlmoiHzABG2/c6KFUGQOfT4Cc6AnNo5YoSzwCJJ/0qNDYnIybqasYY4R1TjaiSlo9ipUcRbMxiVwn7ikKRXyeUzuoMTnneH6G3aRODi5ZntT97zHYsj6Yu4zpjza3M4lA+fYGS3TMDGi7Bp5ELmahJ3QXADjemIUItu5JwUavwwii+QCYfYAx7epxj8hsMlR2Thqk0NoE8BI6avIZJKGTlvjBZ9UB9tLR8WF4KCspF6rtKIJ1nsOfRmHToUAzZOjfA47qT7/GyoN4eNpxfvmuKb4IJg9qVJ/iqYv8FtC/JDn6yu/ezTyc0Y4ibAC9EfraTj9Y1ZWYbx+skFKSVwwhBP6wxZH6TCZ9n1K5fgHmV/LjFPnDc9a+y3ief9KzKEjmjJZrx3yisSBgnaDl4VsE/mnhfRx4SJSZEHSqbrzx2hKCD96KcdrEPKymfwZ4VuoIWDklw3HZcuY9flO6IsPsphXQ7XhyK8nYmjIjKNpe0eh+vgdLFSNteqHQsA0bVWRXdp6vTW7432su119KxmFYW0q4vM9nePTDCLy9eeJ4xiVLCRqzedJXqWMHKjaBg0U8arH12w4hr0CTOZjhREN5GWhp2TrUw2LgC3sDRK1RZqxSduwwwCT7SpcR0htTZpfKDsG9yATX4+XjRFEy747L6SQWRUsCjVm4axN+LicA8BiXLgpH2t0liAdI0O6r/xORaEdc02L0GsdMtI2JSQSiDwMNoiL/cgoJVo+VlRjdehdVX/OnxTWwkebd3JIY0gITa9k4Bix5BzU9i538CDpyj8OXj1doq8RcE2uGRLx6jl2+3KofXiuB7+4ghfTJI8WC9B3vVfG8sLLW82AWA/ENRrx5QnSDx4ckqWYh1eiIU/hv9GbzeDy/06lH+px3oS86HQdSpKQMFhbzdFoMdpG1OQVG/v5Tald6xGonB25Hx/m6iWEJT2oK2m1MMAnjEM23IUsSAPk04yntv0qyVguRssaPdqODPaHZ9Ws+Nfx194klsvoZLnuJ6PeTQVL4HQx2SQGOdUYG7pDYhKs/SmA7BkbBer+O+7hrrT12a6Rgz2I/VMSxw37QQMySCiIXWcT17CIWVpm4CgV759LjBECGeomNTMlFc/THUFF/fEAQjZOVd6b3zqPGXd1s6ZGNZi0STcsXVEy/FjXMyoKkLmeJYM6do7qXg4+/stpnk4rCqkyUN84JNkPBT66PD1mvyaYRyu5wSDh2qtVnYKhr9vlseLfndsZoe1Oloe/hLqP2OlCitJqwI3m6NVwRE10VUZXN0Y29pbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHRlc3Rjb2luIG1ldGFkYXRhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMKAAAAAAAAAL5bHi353bGaHtTpaHv4S6j9jpQorSasCN5ujVcERNdFAFG8pwrph/YwrtYF7DI1R4Mg8zFQqXx4IQwDL6uwc8rnIDuMts+tVlpRJIwbUa9zlMQ3OLkBmjH0WBi/m3Z3UACgNlSU4z2q/6zdyWMjrG3ahiXXYJ78ussvCLKEdygwBAEAAAAAAAAAs4HL/n8iUj1uQhCQWcyqG8B3hhWQwteOEU4nospA5ZAU30K47qUNbIXFDqJJ6Ae8RRFzJP9LNLg4v/rgLxaFBw==" } ] -} \ No newline at end of file +} diff --git a/ironfish/src/primitives/transaction.test.slow.ts b/ironfish/src/primitives/transaction.test.slow.ts index 264621a457..c8daa510a6 100644 --- a/ironfish/src/primitives/transaction.test.slow.ts +++ b/ironfish/src/primitives/transaction.test.slow.ts @@ -2,7 +2,6 @@ * 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 { Asset } from '@ironfish/rust-nodejs' -import { Assert } from '../assert' import { createNodeTest, useAccountFixture, @@ -55,39 +54,6 @@ describe('Transaction', () => { expect(transactionB.isMinersFee()).toBe(true) }) - it('throw error if account is not fully synced when creating transaction', async () => { - const nodeA = nodeTest.node - - // Create an account A - const accountA = await useAccountFixture(nodeA.wallet, 'testA') - const accountB = await useAccountFixture(nodeA.wallet, 'testB') - - // Create a block with a miner's fee - const block1 = await useMinerBlockFixture(nodeA.chain, 2, accountA) - await nodeA.chain.addBlock(block1) - await nodeA.wallet.updateHead() - const headhash = await nodeA.wallet.getLatestHeadHash() - Assert.isNotNull(headhash) - // Modify the headhash - headhash[0] = 0 - await accountA.updateHead({ hash: headhash, sequence: 2 }) - - const response = nodeA.wallet.createTransaction({ - account: accountA, - outputs: [ - { - publicAddress: accountB.publicAddress, - amount: BigInt(1), - memo: '', - assetId: Asset.nativeId(), - }, - ], - fee: 1n, - expiration: 0, - }) - await expect(response).rejects.toThrow(Error) - }) - it('check if a transaction is not a miners fee', async () => { const nodeA = nodeTest.node diff --git a/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture b/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture index d843f0fd1e..121c984c93 100644 --- a/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture +++ b/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture @@ -6739,5 +6739,110 @@ "sequence": 3 } } + ], + "Wallet createTransaction throw error if account is not fully synced when creating transaction": [ + { + "version": 3, + "id": "b59eda2d-904e-4da3-9512-a0b25611b6a3", + "name": "testA", + "spendingKey": "b99e7690ee81d4f4d612c97d084a86de0c429f8f93a88e3cfea555beb3c422a9", + "viewKey": "ffa9c00e7ec0eb9f011064c3604086437c73e03bf5080d75542e24681c8c0ab910fa16526bc6e8fe3981b4aabe38124db9c05ee9155749df24fd13390b50444b", + "incomingViewKey": "1c1860afd44df75687543f5849bac6989ddba81f44cce9a6722cfba26a2b2703", + "outgoingViewKey": "13199aff517e7ce766b37e5e47b7cc821f5f406727b4ea95d9a808009d27013e", + "publicAddress": "4d443ee12dddd05b5545e37fa81f08610eeb80936e22f9b9f941827636944dac", + "createdAt": { + "hash": { + "type": "Buffer", + "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" + }, + "sequence": 1 + } + }, + { + "version": 3, + "id": "3a8c3bd8-e2c5-4192-85d1-83496e6a84e4", + "name": "testB", + "spendingKey": "9826fc36e963313f936f589e7768f819ca171b2c341aa225cd5e40d425c15d96", + "viewKey": "78e7afe54130d3bc2361f4355a35fa5d78f596ea9a288f7eb040ae454fd96aad8a584f8ba43cbc74e18aa0c97ec9a0bd4199a9b3005307c952f4a893864f8213", + "incomingViewKey": "85493e842b596100eb396ca5a725822c2359f01f3074ca67ced32a8754272802", + "outgoingViewKey": "877b98e0a1c99423a7d0f4786be0dcf8df6a61b773d759aaee2e706f67bae7fd", + "publicAddress": "4267eb0d90c2dc6552fd350c23fe006654e170ec480b53e7b5716f433eee6ec9", + "createdAt": { + "hash": { + "type": "Buffer", + "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" + }, + "sequence": 1 + } + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", + "noteCommitment": { + "type": "Buffer", + "data": "base64:VwABPj6VtUEEtRbrxY8KypD/vIv1uJuozXMg6fgdOQ8=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:WaQM4GYX0tgnsONIw/fa7yH7FtEh+x4TIymLW7fQX6I=" + }, + "target": "9282972777491357380673661573939192202192629606981189395159182914949423", + "randomness": "0", + "timestamp": 1706643093240, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AgAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAuZYKZJtTsS/3upu+uR8DVKer039byVNm+2mu5OLC5N2vIS+XIdDiTVjI2oSMlwfj8h+pMCdhr2hWlBWdPkLBGHl/czlT6mfJnJz7CEepv2iAyyVpFaW6kTSAF8CxHH9LzgtYOz5J4yAx5tnH48t3dSBtgftW8GXl77IKulty3B4BR7UCswtRK1m8xlhmRFc94uH3fN//sWrNBIhBV/CKbBJo2dCzvb9+0esCOnH3VRC0Y7JCKo5YCKe5F3Iyvi1ie4rfYbAM2XJ6Z6iwHUsHklUBrSb3DQ3XjQ+IQUoCASAKKv6d45E0Mg5Zh6pl7m3UzPeoiIqLpVWf9pMN33bb01Ka0Myw2nxSpuUJTxgJiW1+kMq8aWXGEMiIy/XnNbkWHJ+PwhPExp03T8xqNXsrPUUX4+reysoP1F/SRkActw9xr4WbM4E960eVwwJIx4cGqi4gVi+bl/q+yQF5ODy+ClXuLw0r7YfqM76vsEBSByYBZ7OtVd5SAc+oVny102SD9rXpDDysoT4pN1IROafTALhfh2o8xRdhR3s4QLxPiyshluMtVOrcE/31sl8jNY5X6ktgvO4TB3oRfgF27Oj4sTOK423/NiPe0c9F5MK4BupQBS6XRs/NZElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwFMk9fpsDi3bFGFxF32GvemKn51q1cPrsY72fDBrgNx5PGGk2UkbfoI92LnyEWmNqv8bWxd1j/pdCxweM+2p5DQ==" + } + ] + }, + { + "version": 3, + "id": "d93bfbd3-a04a-4d93-a108-8e0ba35fff1d", + "name": "test", + "spendingKey": "29967705d77d0af5d965ec9f9ebf1e2bc44e2b8930ca752f36cd7d20fbddfa9f", + "viewKey": "cf863e32b2b8dd6b9486a8b01c12583a9c496be87a939f29ee889cd0dd7aafefca1d61d3282857ddb4f4ac238f41066b5af5a3dc3c0af1f4e303ccd0228263a4", + "incomingViewKey": "e9e4826ebdc483056e1974fdb404147d7bc82762cbe4272c6452037dc5c17203", + "outgoingViewKey": "96071b7f56a836372832ba504ae3e6205a2492403f4a290cc584fe70d570b9f5", + "publicAddress": "203ff42455fa235012dedc7ed32432fccff046f7d30377c77bd18d74da77b098", + "createdAt": { + "hash": { + "type": "Buffer", + "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" + }, + "sequence": 1 + } + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", + "noteCommitment": { + "type": "Buffer", + "data": "base64:0u/eq8yWgI/gqhzY9uC1NyHoixNTXEqBUDi6xZxI8F4=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:7lu9oNB6rV0EZQ0BANOD3P/dAeFVGTZ+33Tzpl7y3L0=" + }, + "target": "9282972777491357380673661573939192202192629606981189395159182914949423", + "randomness": "0", + "timestamp": 1706643093807, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAuJ7/6S1e5kfsCaMlroYuJjGoy9gNrftRzAe8lEwoPd6iwHFshCHEOW3ec3md2f+WrblSkMz5bb77TE1IJHz+v2eTRNsxN1Lg3JXJ6ql+wSqtxW+Uz5l5do1WVOkoT0+Utr99viJiUFJPcoLBLGNu9Ld7z7t8GlMXwQepMLQRwAYSMfxpCyUEzsxFF11tswDfSRX7/xnMOTXYyhurfMYb4sqSEqsWJ78qlbY3hNfribmMGHZOKPcnZQi9sGW6t3LGiVdNxX7Sdja1OpMUe+kWWQyNho6BLK93BT+aWNNMJDQIZcsnretyK32qcLFL92r6B7UscVcXr7WLCM1QiPZ4iIyiOct61p+d0Uo3Ov6yqEj5jg4o0u97LvwrHvYWtY5y0xK8l77n36oMGDl7MNyiGmb4f0Y21xr0wjeh7yniV9L0ceuU1KEN3U/sd9AGP6gY+8b8L/48fZ+Aa7BseW9rrVIszIvOl8m5owfyUbnv3NZ3z/KPQhytSVeTucumnT1vCySLT4wDXQ4ZFRmhOuWjxTYQv5iIUl0gISpuEE9jgfFpEPwyaUOzGVGW6YTCUQL3GO/OVNlJO4hVeoqUMwo+znedJwqwOwuP3naEZebh9j8ZQuJf7dGu2Ulyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwjStaOt2a+kxPBxlPm0nJnBvZm5HHBrwg+QpCX6SHJhGy4p1ru/INeO+XLfO2HAS9OikzxI+rEwVtGbFq0K0WDA==" + } + ] + } ] } \ No newline at end of file diff --git a/ironfish/src/wallet/wallet.test.ts b/ironfish/src/wallet/wallet.test.ts index 659497e77d..65e1ee20de 100644 --- a/ironfish/src/wallet/wallet.test.ts +++ b/ironfish/src/wallet/wallet.test.ts @@ -1218,6 +1218,41 @@ describe('Wallet', () => { expect(reorgVerification.valid).toBe(true) }) + it('throw error if account is not fully synced when creating transaction', async () => { + // Create an account A + const accountA = await useAccountFixture(nodeTest.wallet, 'testA') + const accountB = await useAccountFixture(nodeTest.wallet, 'testB') + + // Create a block with a miner's fee + const block = await useMinerBlockFixture(nodeTest.node.chain, 2, accountA) + await nodeTest.chain.addBlock(block) + await nodeTest.wallet.updateHead() + + // Modify the headhash + const headhash = await nodeTest.wallet.getLatestHeadHash() + Assert.isNotNull(headhash) + headhash[0] = 0 + await accountA.updateHead({ hash: headhash, sequence: 2 }) + + const response = nodeTest.wallet.createTransaction({ + account: accountA, + outputs: [ + { + publicAddress: accountB.publicAddress, + amount: BigInt(1), + memo: '', + assetId: Asset.nativeId(), + }, + ], + fee: 1n, + expiration: 0, + }) + + await expect(response).rejects.toThrow( + 'Your account must finish scanning before sending a transaction.', + ) + }) + describe('should create transactions with the correct version', () => { const preservedNodeTest = createNodeTest(true) let chain: Blockchain From d684cf96e94b9c17cdb6ce1ee24119f87ed751c6 Mon Sep 17 00:00:00 2001 From: jowparks Date: Tue, 30 Jan 2024 13:31:52 -0800 Subject: [PATCH 13/27] updating signing commitment type to be array instead of hashmap to match types in TS layer (cleaner representation) (#4631) --- ironfish-rust-nodejs/index.d.ts | 10 +++++-- ironfish-rust-nodejs/src/frost.rs | 14 ++++++--- .../src/structs/transaction.rs | 14 +++++---- .../multisig/createSigningPackage.test.ts | 4 +-- .../routes/multisig/createSigningPackage.ts | 11 ++----- ironfish/src/wallet/wallet.test.slow.ts | 29 +++++++++---------- 6 files changed, 43 insertions(+), 39 deletions(-) diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index f7e04a99fc..f54cb713ef 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -3,11 +3,15 @@ /* auto-generated by NAPI-RS */ -export interface SigningCommitments { +export interface Commitment { hiding: string binding: string } -export function roundOne(keyPackage: string, seed: number): SigningCommitments +export interface IdentifierCommitment { + identifier: string + commitment: Commitment +} +export function roundOne(keyPackage: string, seed: number): Commitment export function roundTwo(signingPackage: string, keyPackage: string, publicKeyRandomness: string, seed: number): string export function splitSecret(coordinatorSaplingKey: string, minSigners: number, maxSigners: number, identifiers: Array): TrustedDealerKeyPackages export function contribute(inputPath: string, outputPath: string, seed?: string | undefined | null): Promise @@ -246,7 +250,7 @@ export class UnsignedTransaction { constructor(jsBytes: Buffer) serialize(): Buffer publicKeyRandomness(): string - signingPackage(nativeCommitments: Record): string + signingPackage(nativeIdentiferCommitments: Array): string sign(spenderHexKey: string): Buffer signFrost(publicKeyPackageStr: string, signingPackageStr: string, signatureSharesMap: Record): Buffer } diff --git a/ironfish-rust-nodejs/src/frost.rs b/ironfish-rust-nodejs/src/frost.rs index d425272843..2a7bccdf74 100644 --- a/ironfish-rust-nodejs/src/frost.rs +++ b/ironfish-rust-nodejs/src/frost.rs @@ -19,19 +19,25 @@ use napi::{bindgen_prelude::*, JsBuffer}; use napi_derive::napi; use rand::thread_rng; -#[napi(object, js_name = "SigningCommitments")] -pub struct NativeSigningCommitments { +#[napi(object, js_name = "Commitment")] +pub struct NativeCommitment { pub hiding: String, pub binding: String, } +#[napi(object, js_name = "IdentifierCommitment")] +pub struct NativeIdentifierCommitment { + pub identifier: String, + pub commitment: NativeCommitment, +} + #[napi] -pub fn round_one(key_package: String, seed: u32) -> Result { +pub fn round_one(key_package: String, seed: u32) -> Result { let key_package = KeyPackage::deserialize(&hex_to_vec_bytes(&key_package).map_err(to_napi_err)?) .map_err(to_napi_err)?; let (_, commitment) = round_one_rust(&key_package, seed as u64); - Ok(NativeSigningCommitments { + Ok(NativeCommitment { hiding: bytes_to_hex(&commitment.hiding().serialize()), binding: bytes_to_hex(&commitment.binding().serialize()), }) diff --git a/ironfish-rust-nodejs/src/structs/transaction.rs b/ironfish-rust-nodejs/src/structs/transaction.rs index c8788da645..3d7459df09 100644 --- a/ironfish-rust-nodejs/src/structs/transaction.rs +++ b/ironfish-rust-nodejs/src/structs/transaction.rs @@ -32,7 +32,8 @@ use napi::{ }; use napi_derive::napi; -use crate::{frost::NativeSigningCommitments, to_napi_err}; +use crate::frost::NativeIdentifierCommitment; +use crate::to_napi_err; use super::note::NativeNote; use super::spend_proof::NativeSpendDescription; @@ -412,21 +413,22 @@ impl NativeUnsignedTransaction { #[napi] pub fn signing_package( &self, - native_commitments: HashMap, + native_identifer_commitments: Vec, ) -> Result { let mut commitments: BTreeMap = BTreeMap::new(); - for (identifier_hex, commitment_hex) in native_commitments { - let identifier_bytes = hex_to_bytes(&identifier_hex).map_err(to_napi_err)?; + for identifier_commitment in native_identifer_commitments { + let identifier_bytes = + hex_to_bytes(&identifier_commitment.identifier).map_err(to_napi_err)?; let identifier = Identifier::deserialize(&identifier_bytes).map_err(to_napi_err)?; let commitment = SigningCommitments::new( NonceCommitment::deserialize( - hex_to_bytes(&commitment_hex.hiding).map_err(to_napi_err)?, + hex_to_bytes(&identifier_commitment.commitment.hiding).map_err(to_napi_err)?, ) .map_err(to_napi_err)?, NonceCommitment::deserialize( - hex_to_bytes(&commitment_hex.binding).map_err(to_napi_err)?, + hex_to_bytes(&identifier_commitment.commitment.binding).map_err(to_napi_err)?, ) .map_err(to_napi_err)?, ); diff --git a/ironfish/src/rpc/routes/multisig/createSigningPackage.test.ts b/ironfish/src/rpc/routes/multisig/createSigningPackage.test.ts index 969eb3ef9d..012bf27c02 100644 --- a/ironfish/src/rpc/routes/multisig/createSigningPackage.test.ts +++ b/ironfish/src/rpc/routes/multisig/createSigningPackage.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 { ParticipantSecret, SigningCommitments } from '@ironfish/rust-nodejs' +import { IdentifierCommitment, ParticipantSecret } from '@ironfish/rust-nodejs' import { createNodeTest, useAccountFixture, @@ -30,7 +30,7 @@ describe('Route multisig/createSigningPackage', () => { const trustedDealerPackage = response.content - const commitments: Array<{ identifier: string; commitment: SigningCommitments }> = [] + const commitments: Array = [] for (let i = 0; i < 3; i++) { const commitment = await routeTest.client.multisig.createSigningCommitment({ keyPackage: trustedDealerPackage.keyPackages[i].keyPackage, diff --git a/ironfish/src/rpc/routes/multisig/createSigningPackage.ts b/ironfish/src/rpc/routes/multisig/createSigningPackage.ts index cc9c4b1c48..12e49aa0d7 100644 --- a/ironfish/src/rpc/routes/multisig/createSigningPackage.ts +++ b/ironfish/src/rpc/routes/multisig/createSigningPackage.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 { SigningCommitments, UnsignedTransaction } from '@ironfish/rust-nodejs' +import { UnsignedTransaction } from '@ironfish/rust-nodejs' import * as yup from 'yup' import { ApiNamespace } from '../namespaces' import { routes } from '../router' @@ -50,14 +50,7 @@ routes.register { - acc[identifier] = commitment - return acc - }, - {}, - ) - const signingPackage = unsignedTransaction.signingPackage(map) + const signingPackage = unsignedTransaction.signingPackage(request.data.commitments) request.end({ signingPackage, diff --git a/ironfish/src/wallet/wallet.test.slow.ts b/ironfish/src/wallet/wallet.test.slow.ts index d4bb0f5f46..d4fd8d8353 100644 --- a/ironfish/src/wallet/wallet.test.slow.ts +++ b/ironfish/src/wallet/wallet.test.slow.ts @@ -8,7 +8,6 @@ import { ParticipantSecret, roundOne, roundTwo, - SigningCommitments, splitSecret, TrustedDealerKeyPackages, } from '@ironfish/rust-nodejs' @@ -1209,20 +1208,20 @@ describe('Wallet', () => { Assert.isNotUndefined(participantB.multiSigKeys) Assert.isNotUndefined(participantC.multiSigKeys) - const signingCommitments: Record = { - [participantA.multiSigKeys.identifier]: roundOne( - participantA.multiSigKeys.keyPackage, - seed, - ), - [participantB.multiSigKeys.identifier]: roundOne( - participantB.multiSigKeys.keyPackage, - seed, - ), - [participantC.multiSigKeys.identifier]: roundOne( - participantC.multiSigKeys.keyPackage, - seed, - ), - } + const signingCommitments = [ + { + identifier: participantA.multiSigKeys.identifier, + commitment: roundOne(participantA.multiSigKeys.keyPackage, seed), + }, + { + identifier: participantB.multiSigKeys.identifier, + commitment: roundOne(participantB.multiSigKeys.keyPackage, seed), + }, + { + identifier: participantC.multiSigKeys.identifier, + commitment: roundOne(participantC.multiSigKeys.keyPackage, seed), + }, + ] // mine block to send IRON to multisig account const miner = await useAccountFixture(node.wallet, 'miner') From 3f3f4cea6388be55664743555f0ce8bee0d0d679 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Tue, 30 Jan 2024 14:46:00 -0800 Subject: [PATCH 14/27] Move createMinersFee() to Blockchain (#4634) --- ironfish/src/blockchain/blockchain.test.ts | 15 ++- ironfish/src/blockchain/blockchain.ts | 31 +++++ ironfish/src/genesis/genesis.test.slow.ts | 11 +- ironfish/src/mining/manager.ts | 2 +- ironfish/src/mining/minersFeeCache.ts | 4 +- .../src/primitives/transaction.test.slow.ts | 66 +++++++--- .../miner/blockTemplateStream.test.slow.ts | 9 +- ironfish/src/strategy.test.slow.ts | 115 +----------------- ironfish/src/strategy.ts | 30 ----- ironfish/src/testUtilities/fixtures/blocks.ts | 8 +- .../testUtilities/fixtures/transactions.ts | 2 +- ironfish/src/wallet/wallet.test.slow.ts | 48 ++++---- ironfish/src/workerPool/job.test.slow.ts | 4 +- 13 files changed, 136 insertions(+), 209 deletions(-) diff --git a/ironfish/src/blockchain/blockchain.test.ts b/ironfish/src/blockchain/blockchain.test.ts index cd011a2db1..6720ceb709 100644 --- a/ironfish/src/blockchain/blockchain.test.ts +++ b/ironfish/src/blockchain/blockchain.test.ts @@ -1737,7 +1737,7 @@ describe('Blockchain', () => { return node.chain.newBlock( [burnTransaction, spendTransaction], - await node.strategy.createMinersFee(fee, 3, generateKey().spendingKey), + await node.chain.createMinersFee(fee, 3, generateKey().spendingKey), ) }) @@ -1939,4 +1939,17 @@ describe('Blockchain', () => { } }) }) + + describe('createMinersFee()', () => { + it('Creates transactions with the correct version based on the sequence', async () => { + const spendingKey = generateKey().spendingKey + nodeTest.chain.consensus.parameters.enableAssetOwnership = 1234 + + const minersFee1 = await nodeTest.chain.createMinersFee(0n, 1233, spendingKey) + const minersFee2 = await nodeTest.chain.createMinersFee(0n, 1234, spendingKey) + + expect(minersFee1.version()).toEqual(TransactionVersion.V1) + expect(minersFee2.version()).toEqual(TransactionVersion.V2) + }) + }) }) diff --git a/ironfish/src/blockchain/blockchain.ts b/ironfish/src/blockchain/blockchain.ts index 3300dd501a..6cc4a5afad 100644 --- a/ironfish/src/blockchain/blockchain.ts +++ b/ironfish/src/blockchain/blockchain.ts @@ -65,6 +65,7 @@ export class Blockchain { seedGenesisBlock: SerializedBlock config: Config blockHasher: BlockHasher + workerPool: WorkerPool readonly blockchainDb: BlockchainDB readonly notes: MerkleTree< @@ -165,6 +166,7 @@ export class Blockchain { this.seedGenesisBlock = options.genesis this.config = options.config this.blockHasher = options.blockHasher + this.workerPool = options.workerPool this.blockchainDb = new BlockchainDB({ files: options.files, @@ -1480,6 +1482,35 @@ export class Blockchain { return asset || null } + /** + * Create the miner's fee transaction for a given block. + * + * The miner's fee is a special transaction with one output and + * zero spends. Its output value must be the total transaction fees + * in the block plus the mining reward for the block. + * + * The mining reward may change over time, so we accept the block sequence + * to calculate the mining reward from. + * + * @param totalTransactionFees is the sum of the transaction fees intended to go + * in this block. + * @param blockSequence the sequence of the block for which the miner's fee is being created + * @param minerKey the spending key for the miner. + */ + async createMinersFee( + totalTransactionFees: bigint, + blockSequence: number, + minerSpendKey: string, + ): Promise { + // Create a new note with value equal to the inverse of the sum of the + // transaction fees and the mining reward + const reward = this.strategy.miningReward(blockSequence) + const amount = totalTransactionFees + BigInt(reward) + + const transactionVersion = this.consensus.getActiveTransactionVersion(blockSequence) + return this.workerPool.createMinersFee(minerSpendKey, amount, '', transactionVersion) + } + newBlockHeaderFromRaw( raw: RawBlockHeader, noteSize?: number | null, diff --git a/ironfish/src/genesis/genesis.test.slow.ts b/ironfish/src/genesis/genesis.test.slow.ts index 8b1831e3ba..d38ffca369 100644 --- a/ironfish/src/genesis/genesis.test.slow.ts +++ b/ironfish/src/genesis/genesis.test.slow.ts @@ -29,7 +29,7 @@ describe('Read genesis block', () => { it('Can start a chain with the existing genesis block', async () => { // We should also be able to create new blocks after the genesis block // has been added - const minersfee = await nodeTest.strategy.createMinersFee( + const minersfee = await nodeTest.chain.createMinersFee( BigInt(0), nodeTest.chain.head.sequence + 1, generateKey().spendingKey, @@ -56,7 +56,6 @@ describe('Create genesis block', () => { it('Can generate a valid genesis block', async () => { // Initialize the database and chain - const strategy = nodeTest.strategy const node = nodeTest.node const chain = nodeTest.chain @@ -103,7 +102,7 @@ describe('Create genesis block', () => { }) // Ensure we can construct blocks after that block - const minersfee = await strategy.createMinersFee( + const minersfee = await chain.createMinersFee( BigInt(0), block.header.sequence + 1, generateKey().spendingKey, @@ -141,7 +140,7 @@ describe('Create genesis block', () => { }) // Ensure we can construct blocks after that block - const newMinersfee = await strategy.createMinersFee( + const newMinersfee = await chain.createMinersFee( BigInt(0), deserializedBlock.header.sequence + 1, generateKey().spendingKey, @@ -261,7 +260,7 @@ describe('addGenesisTransaction', () => { }) // Create a new node - const { strategy, chain, node } = await nodeTest.createSetup() + const { chain, node } = await nodeTest.createSetup() // Import accounts const account1 = await node.wallet.importAccount(account1Original) @@ -295,7 +294,7 @@ describe('addGenesisTransaction', () => { }) // Ensure we can construct blocks after that block - const minersfee = await strategy.createMinersFee( + const minersfee = await chain.createMinersFee( BigInt(0), block.header.sequence + 1, generateKey().spendingKey, diff --git a/ironfish/src/mining/manager.ts b/ironfish/src/mining/manager.ts index 12cdc6f0cc..b2b5a39405 100644 --- a/ironfish/src/mining/manager.ts +++ b/ironfish/src/mining/manager.ts @@ -323,7 +323,7 @@ export class MiningManager { currBlockSize = newBlockSize // Calculate the final fee for the miner of this block - minersFee = await this.node.strategy.createMinersFee( + minersFee = await this.node.chain.createMinersFee( totalFees, newBlockSequence, account.spendingKey, diff --git a/ironfish/src/mining/minersFeeCache.ts b/ironfish/src/mining/minersFeeCache.ts index ca107163f8..2dc84c3dc0 100644 --- a/ironfish/src/mining/minersFeeCache.ts +++ b/ironfish/src/mining/minersFeeCache.ts @@ -34,7 +34,7 @@ export class MinersFeeCache { return cached } - const minersFeePromise = this.node.strategy.createMinersFee( + const minersFeePromise = this.node.chain.createMinersFee( BigInt(0), sequence, account.spendingKey, @@ -48,7 +48,7 @@ export class MinersFeeCache { startCreatingEmptyMinersFee(sequence: number, account: SpendingAccount): void { const key = `${sequence}-${account.publicAddress}` - const minersFeePromise = this.node.strategy.createMinersFee( + const minersFeePromise = this.node.chain.createMinersFee( BigInt(0), sequence, account.spendingKey, diff --git a/ironfish/src/primitives/transaction.test.slow.ts b/ironfish/src/primitives/transaction.test.slow.ts index c8daa510a6..de31f3b4bf 100644 --- a/ironfish/src/primitives/transaction.test.slow.ts +++ b/ironfish/src/primitives/transaction.test.slow.ts @@ -1,7 +1,8 @@ /* 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 { Asset } from '@ironfish/rust-nodejs' +import { Asset, generateKey } from '@ironfish/rust-nodejs' +import { Assert } from '../assert' import { createNodeTest, useAccountFixture, @@ -17,17 +18,9 @@ describe('Transaction', () => { it('produces unique transaction hashes', async () => { const account = await useAccountFixture(nodeTest.wallet) - const transactionA = await nodeTest.strategy.createMinersFee( - BigInt(0), - 1, - account.spendingKey, - ) + const transactionA = await nodeTest.chain.createMinersFee(BigInt(0), 1, account.spendingKey) - const transactionB = await nodeTest.strategy.createMinersFee( - BigInt(0), - 1, - account.spendingKey, - ) + const transactionB = await nodeTest.chain.createMinersFee(BigInt(0), 1, account.spendingKey) const hashA = transactionA.unsignedHash() const hashB = transactionB.unsignedHash() @@ -38,13 +31,9 @@ describe('Transaction', () => { it('check if a transaction is a miners fee', async () => { const account = await useAccountFixture(nodeTest.wallet) - const transactionA = await nodeTest.strategy.createMinersFee( - BigInt(0), - 1, - account.spendingKey, - ) + const transactionA = await nodeTest.chain.createMinersFee(BigInt(0), 1, account.spendingKey) - const transactionB = await nodeTest.strategy.createMinersFee( + const transactionB = await nodeTest.chain.createMinersFee( BigInt(-1), 1, account.spendingKey, @@ -165,4 +154,47 @@ describe('Transaction', () => { value: burnAmount, }) }) + + it('Does not hold a posted transaction if no references are taken', async () => { + const spendingKey = generateKey().spendingKey + const tx = await nodeTest.chain.createMinersFee(0n, 0, spendingKey) + const valid = await nodeTest.workerPool.verifyTransactions([tx]) + + expect(valid).toMatchObject({ valid: true }) + expect(tx['transactionPosted']).toBeNull() + }) + + it('Holds a posted transaction if a reference is taken', async () => { + const spendingKey = generateKey().spendingKey + const tx = await nodeTest.chain.createMinersFee(0n, 0, spendingKey) + + await tx.withReference(async () => { + expect(tx['transactionPosted']).not.toBeNull() + expect(tx.notes.length).toEqual(1) + expect(tx['transactionPosted']).not.toBeNull() + + // Reference returning happens on the promise jobs queue, so use an await + // to delay until reference returning is expected to happen + return Promise.resolve() + }) + + expect(tx['transactionPosted']).toBeNull() + }) + + it('Does not hold a note if no references are taken', async () => { + const key = generateKey() + const minersFee = await nodeTest.chain.createMinersFee(0n, 0, key.spendingKey) + expect(minersFee['transactionPosted']).toBeNull() + + const note = minersFee.notes[0] ?? null + expect(note).not.toBeNull() + expect(note['noteEncrypted']).toBeNull() + + const decryptedNote = note.decryptNoteForOwner(key.incomingViewKey) + Assert.isNotUndefined(decryptedNote, 'Note must be decryptable') + expect(note['noteEncrypted']).toBeNull() + expect(decryptedNote['note']).toBeNull() + expect(decryptedNote.value()).toBe(2000000000n) + expect(decryptedNote['note']).toBeNull() + }) }) diff --git a/ironfish/src/rpc/routes/miner/blockTemplateStream.test.slow.ts b/ironfish/src/rpc/routes/miner/blockTemplateStream.test.slow.ts index eaff7d992e..681c858b3d 100644 --- a/ironfish/src/rpc/routes/miner/blockTemplateStream.test.slow.ts +++ b/ironfish/src/rpc/routes/miner/blockTemplateStream.test.slow.ts @@ -88,12 +88,12 @@ describe('Block template stream', () => { await nodeTest.teardownAll() // Now, spy on some functions - const actual = node.strategy.createMinersFee + const actual = node.chain.createMinersFee.bind(node.chain) const [p, res] = PromiseUtils.split() - jest.spyOn(node.strategy, 'createMinersFee').mockImplementation(async (a, b, c) => { + jest.spyOn(node.chain, 'createMinersFee').mockImplementation(async (a, b, c) => { await p - return await actual.bind(node.strategy)(a, b, c) + return await actual(a, b, c) }) const newBlockSpy = jest.spyOn(node.chain, 'newBlock') @@ -108,7 +108,7 @@ describe('Block template stream', () => { await expect(chain).toAddBlock(block2) await expect(chain).toAddBlock(block3) - // Resolve the createMinersSpy promise, allowing block creation to proceed to newBlock + // Resolve the createMinersFee promise, allowing block creation to proceed to newBlock res() // Finish up the response @@ -116,6 +116,7 @@ describe('Block template stream', () => { await expect(response.waitForRoute()).resolves.toEqual(expect.anything()) // newBlock should have thrown an error, but the response should not have crashed + expect(newBlockSpy).toHaveBeenCalled() await expect(newBlockSpy.mock.results[0].value).rejects.toThrow() }) }) diff --git a/ironfish/src/strategy.test.slow.ts b/ironfish/src/strategy.test.slow.ts index 48db482df3..9891adffc6 100644 --- a/ironfish/src/strategy.test.slow.ts +++ b/ironfish/src/strategy.test.slow.ts @@ -11,8 +11,6 @@ import { Transaction as NativeTransaction, TransactionPosted as NativeTransactionPosted, } from '@ironfish/rust-nodejs' -import { BlockHasher } from './blockHasher' -import { Consensus, ConsensusParameters } from './consensus' import { MerkleTree } from './merkletree' import { LeafEncoding } from './merkletree/database/leaves' import { NodeEncoding } from './merkletree/database/nodes' @@ -20,10 +18,7 @@ import { NoteHasher } from './merkletree/hasher' import { Transaction } from './primitives' import { Note } from './primitives/note' import { NoteEncrypted, NoteEncryptedHash } from './primitives/noteEncrypted' -import { TransactionVersion } from './primitives/transaction' import { BUFFER_ENCODING, IDatabase } from './storage' -import { Strategy } from './strategy' -import { FISH_HASH_CONTEXT } from './testUtilities' import { makeDb, makeDbName } from './testUtilities/helpers/storage' import { WorkerPool } from './workerPool' @@ -63,20 +58,6 @@ async function makeStrategyTree({ return tree } -type ThenArg = T extends PromiseLike ? U : T - -const consensusParameters: ConsensusParameters = { - allowedBlockFutureSeconds: 15, - genesisSupplyInIron: 42000000, - targetBlockTimeInSeconds: 60, - targetBucketTimeInSeconds: 10, - maxBlockSizeBytes: 512 * 1024, - minFee: 1, - enableAssetOwnership: 1, - enforceSequentialBlockTime: 3, - enableFishHash: 'never', -} - /** * Tests whether it's possible to create a miner reward and transfer those funds * to another account using ironfish-rust transactions + strategy. @@ -85,7 +66,7 @@ const consensusParameters: ConsensusParameters = { * blocks inside the test. */ describe('Demonstrate the Sapling API', () => { - let tree: ThenArg> + let tree: MerkleTree let receiverKey: Key let spenderKey: Key let minerNote: NativeNote @@ -93,7 +74,6 @@ describe('Demonstrate the Sapling API', () => { let transaction: NativeTransaction let publicTransaction: NativeTransactionPosted let workerPool: WorkerPool - let strategy: Strategy beforeAll(async () => { // Pay the cost of setting up Sapling and the DB outside of any test @@ -101,13 +81,6 @@ describe('Demonstrate the Sapling API', () => { spenderKey = generateKey() receiverKey = generateKey() workerPool = new WorkerPool() - const consensus = new Consensus(consensusParameters) - const blockHasher = new BlockHasher({ consensus, context: FISH_HASH_CONTEXT }) - strategy = new Strategy({ - workerPool, - consensus, - blockHasher, - }) }) describe('Can transact between two accounts', () => { @@ -196,92 +169,6 @@ describe('Demonstrate the Sapling API', () => { }) }) - describe('Serializes and deserializes transactions', () => { - it('Does not hold a posted transaction if no references are taken', async () => { - // Generate a miner's fee transaction - const minersFee = await strategy.createMinersFee(0n, 0, generateKey().spendingKey) - - expect(minersFee['transactionPosted']).toBeNull() - expect(await workerPool.verifyTransactions([minersFee])).toEqual({ valid: true }) - expect(minersFee['transactionPosted']).toBeNull() - }) - - it('Holds a posted transaction if a reference is taken', async () => { - // Generate a miner's fee transaction - const minersFee = await strategy.createMinersFee(0n, 0, generateKey().spendingKey) - - await minersFee.withReference(async () => { - expect(minersFee['transactionPosted']).not.toBeNull() - - expect(minersFee.notes.length).toEqual(1) - expect(minersFee['transactionPosted']).not.toBeNull() - - // Reference returning happens on the promise jobs queue, so use an await - // to delay until reference returning is expected to happen - return Promise.resolve() - }) - - expect(minersFee['transactionPosted']).toBeNull() - }) - - it('Does not hold a note if no references are taken', async () => { - // Generate a miner's fee transaction - const key = generateKey() - const minersFee = await strategy.createMinersFee(0n, 0, key.spendingKey) - - expect(minersFee['transactionPosted']).toBeNull() - - const note: NoteEncrypted | null = minersFee.notes[0] ?? null - - if (note === null) { - throw new Error('Must have at least one note') - } - - expect(note['noteEncrypted']).toBeNull() - const decryptedNote = note.decryptNoteForOwner(key.incomingViewKey) - expect(decryptedNote).toBeDefined() - expect(note['noteEncrypted']).toBeNull() - - if (decryptedNote === undefined) { - throw new Error('Note must be decryptable') - } - - expect(decryptedNote['note']).toBeNull() - expect(decryptedNote.value()).toBe(2000000000n) - expect(decryptedNote['note']).toBeNull() - }) - - it('Creates transactions with the correct version based on the sequence', async () => { - const modifiedParams = { - ...consensusParameters, - enableAssetOwnership: 1234, - } - - const key = generateKey() - const consensus = new Consensus(modifiedParams) - const blockHasher = new BlockHasher({ consensus, context: FISH_HASH_CONTEXT }) - const modifiedStrategy = new Strategy({ - workerPool, - consensus, - blockHasher, - }) - - const minersFee1 = await modifiedStrategy.createMinersFee( - 0n, - modifiedParams.enableAssetOwnership - 1, - key.spendingKey, - ) - expect(minersFee1.version()).toEqual(TransactionVersion.V1) - - const minersFee2 = await modifiedStrategy.createMinersFee( - 0n, - modifiedParams.enableAssetOwnership, - key.spendingKey, - ) - expect(minersFee2.version()).toEqual(TransactionVersion.V2) - }) - }) - describe('Finding notes to spend', () => { let receiverNote: Note const receiverWitnessIndex = 1 diff --git a/ironfish/src/strategy.ts b/ironfish/src/strategy.ts index 673bab397d..d5d35df711 100644 --- a/ironfish/src/strategy.ts +++ b/ironfish/src/strategy.ts @@ -3,7 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { BlockHasher } from './blockHasher' import { Consensus } from './consensus' -import { Transaction } from './primitives/transaction' import { MathUtils } from './utils' import { WorkerPool } from './workerPool' @@ -69,33 +68,4 @@ export class Strategy { convertIronToOre(iron: number): number { return Math.round(iron * 10 ** 8) } - - /** - * Create the miner's fee transaction for a given block. - * - * The miner's fee is a special transaction with one output and - * zero spends. Its output value must be the total transaction fees - * in the block plus the mining reward for the block. - * - * The mining reward may change over time, so we accept the block sequence - * to calculate the mining reward from. - * - * @param totalTransactionFees is the sum of the transaction fees intended to go - * in this block. - * @param blockSequence the sequence of the block for which the miner's fee is being created - * @param minerKey the spending key for the miner. - */ - async createMinersFee( - totalTransactionFees: bigint, - blockSequence: number, - minerSpendKey: string, - ): Promise { - // Create a new note with value equal to the inverse of the sum of the - // transaction fees and the mining reward - const amount = totalTransactionFees + BigInt(this.miningReward(blockSequence)) - - const transactionVersion = this.consensus.getActiveTransactionVersion(blockSequence) - - return this.workerPool.createMinersFee(minerSpendKey, amount, '', transactionVersion) - } } diff --git a/ironfish/src/testUtilities/fixtures/blocks.ts b/ironfish/src/testUtilities/fixtures/blocks.ts index 2f6bd5e17c..0b632a9313 100644 --- a/ironfish/src/testUtilities/fixtures/blocks.ts +++ b/ironfish/src/testUtilities/fixtures/blocks.ts @@ -80,7 +80,7 @@ export async function useMinerBlockFixture( async () => chain.newBlock( transactions, - await chain.strategy.createMinersFee( + await chain.createMinersFee( transactionFees, sequence || chain.head.sequence + 1, spendingKey, @@ -197,7 +197,7 @@ export async function useBlockWithRawTxFixture( return chain.newBlock( [transaction], - await chain.strategy.createMinersFee(transaction.fee(), sequence, sender.spendingKey), + await chain.createMinersFee(transaction.fee(), sequence, sender.spendingKey), ) } @@ -269,7 +269,7 @@ export async function useBlockWithTx( return node.chain.newBlock( [transaction], - await node.strategy.createMinersFee(transaction.fee(), 3, generateKey().spendingKey), + await node.chain.createMinersFee(transaction.fee(), 3, generateKey().spendingKey), ) }) @@ -335,7 +335,7 @@ export async function useBlockWithCustomTxs( return node.chain.newBlock( transactions, - await node.strategy.createMinersFee(transactionFees, 3, generateKey().spendingKey), + await node.chain.createMinersFee(transactionFees, 3, generateKey().spendingKey), ) }, node.wallet, diff --git a/ironfish/src/testUtilities/fixtures/transactions.ts b/ironfish/src/testUtilities/fixtures/transactions.ts index 8eb4adc256..30bc1099a8 100644 --- a/ironfish/src/testUtilities/fixtures/transactions.ts +++ b/ironfish/src/testUtilities/fixtures/transactions.ts @@ -156,7 +156,7 @@ export async function useMinersTxFixture( Assert.isNotUndefined(to) Assert.isNotNull(to.spendingKey) - return node.chain.strategy.createMinersFee( + return node.chain.createMinersFee( BigInt(amount), sequence || node.chain.head.sequence + 1, to.spendingKey, diff --git a/ironfish/src/wallet/wallet.test.slow.ts b/ironfish/src/wallet/wallet.test.slow.ts index d4fd8d8353..0dc74e3864 100644 --- a/ironfish/src/wallet/wallet.test.slow.ts +++ b/ironfish/src/wallet/wallet.test.slow.ts @@ -72,7 +72,7 @@ describe('Wallet', () => { }) // Create a block with a miner's fee - const minersfee = await nodeTest.strategy.createMinersFee(BigInt(0), 2, account.spendingKey) + const minersfee = await nodeTest.chain.createMinersFee(BigInt(0), 2, account.spendingKey) const newBlock = await chain.newBlock([], minersfee) const addResult = await chain.addBlock(newBlock) expect(addResult.isAdded).toBeTruthy() @@ -88,7 +88,6 @@ describe('Wallet', () => { it('Lowers the balance after using send to spend a note', async () => { // Initialize the database and chain - const strategy = nodeTest.strategy const node = nodeTest.node const chain = nodeTest.chain @@ -108,7 +107,7 @@ describe('Wallet', () => { }) // Create a block with a miner's fee - const minersfee = await strategy.createMinersFee(BigInt(0), 2, account.spendingKey) + const minersfee = await chain.createMinersFee(BigInt(0), 2, account.spendingKey) const newBlock = await chain.newBlock([], minersfee) const addResult = await chain.addBlock(newBlock) expect(addResult.isAdded).toBeTruthy() @@ -137,7 +136,7 @@ describe('Wallet', () => { }) // Create a block with a miner's fee - const minersfee2 = await strategy.createMinersFee( + const minersfee2 = await chain.createMinersFee( transaction.fee(), newBlock.header.sequence + 1, generateKey().spendingKey, @@ -156,7 +155,6 @@ describe('Wallet', () => { it('Creates valid transactions when the worker pool is enabled', async () => { // Initialize the database and chain - const strategy = nodeTest.strategy const node = nodeTest.node const chain = nodeTest.chain @@ -176,7 +174,7 @@ describe('Wallet', () => { }) // Create a block with a miner's fee - const minersfee = await strategy.createMinersFee(BigInt(0), 2, account.spendingKey) + const minersfee = await chain.createMinersFee(BigInt(0), 2, account.spendingKey) const newBlock = await chain.newBlock([], minersfee) const addResult = await chain.addBlock(newBlock) expect(addResult.isAdded).toBeTruthy() @@ -208,7 +206,7 @@ describe('Wallet', () => { ) // Create a block with a miner's fee - const minersfee2 = await strategy.createMinersFee( + const minersfee2 = await chain.createMinersFee( transaction.fee(), newBlock.header.sequence + 1, generateKey().spendingKey, @@ -227,7 +225,6 @@ describe('Wallet', () => { it('creates valid transactions with multiple outputs', async () => { // Initialize the database and chain - const strategy = nodeTest.strategy const node = nodeTest.node const chain = nodeTest.chain @@ -247,7 +244,7 @@ describe('Wallet', () => { }) // Create a block with a miner's fee - const minersfee = await strategy.createMinersFee(BigInt(0), 2, account.spendingKey) + const minersfee = await chain.createMinersFee(BigInt(0), 2, account.spendingKey) const newBlock = await chain.newBlock([], minersfee) const addResult = await chain.addBlock(newBlock) expect(addResult.isAdded).toBeTruthy() @@ -290,7 +287,7 @@ describe('Wallet', () => { ) // Create a block with a miner's fee - const minersfee2 = await strategy.createMinersFee( + const minersfee2 = await chain.createMinersFee( transaction.fee(), newBlock.header.sequence + 1, generateKey().spendingKey, @@ -332,7 +329,6 @@ describe('Wallet', () => { it('Expires transactions when calling expireTransactions', async () => { // Initialize the database and chain - const strategy = nodeTest.strategy const node = nodeTest.node const chain = nodeTest.chain @@ -355,7 +351,7 @@ describe('Wallet', () => { }) // Create a block with a miner's fee - const minersfee = await strategy.createMinersFee(BigInt(0), 2, account.spendingKey) + const minersfee = await chain.createMinersFee(BigInt(0), 2, account.spendingKey) const newBlock = await chain.newBlock([], minersfee) const addResult = await chain.addBlock(newBlock) expect(addResult.isAdded).toBeTruthy() @@ -396,7 +392,7 @@ describe('Wallet', () => { }) // Create a block with a miner's fee - const minersfee2 = await strategy.createMinersFee( + const minersfee2 = await chain.createMinersFee( transaction.fee(), newBlock.header.sequence + 1, generateKey().spendingKey, @@ -415,7 +411,6 @@ describe('Wallet', () => { it('Expires transactions when calling expireTransactions with restarts', async () => { // Initialize the database and chain - const strategy = nodeTest.strategy const node = nodeTest.node const chain = nodeTest.chain @@ -441,7 +436,7 @@ describe('Wallet', () => { }) // Create a block with a miner's fee - const minersfee = await strategy.createMinersFee(BigInt(0), 2, account.spendingKey) + const minersfee = await chain.createMinersFee(BigInt(0), 2, account.spendingKey) const newBlock = await chain.newBlock([], minersfee) await expect(chain).toAddBlock(newBlock) @@ -478,7 +473,7 @@ describe('Wallet', () => { await node.wallet.open() // Create a block with a miner's fee - const minersfee2 = await strategy.createMinersFee( + const minersfee2 = await chain.createMinersFee( transaction.fee(), newBlock.header.sequence + 1, generateKey().spendingKey, @@ -536,7 +531,7 @@ describe('Wallet', () => { // Create block 2 return nodeA.chain.newBlock( [transaction], - await nodeA.strategy.createMinersFee(transaction.fee(), 3, generateKey().spendingKey), + await nodeA.chain.createMinersFee(transaction.fee(), 3, generateKey().spendingKey), ) }) @@ -631,7 +626,6 @@ describe('Wallet', () => { it('View only accounts can observe received and spent notes', async () => { // Initialize the database and chain - const strategy = nodeTest.strategy const node = nodeTest.node // need separate node because a node cannot have a view only wallet + spend wallet for same account const { node: viewOnlyNode } = await nodeTest.createSetup() @@ -653,7 +647,7 @@ describe('Wallet', () => { const viewOnlyAccount = await viewOnlyNode.wallet.importAccount(accountValue) // Create a block with a miner's fee - const minersfee = await strategy.createMinersFee(BigInt(0), 2, account.spendingKey) + const minersfee = await chain.createMinersFee(BigInt(0), 2, account.spendingKey) const newBlock = await chain.newBlock([], minersfee) const addResult = await chain.addBlock(newBlock) const addResultViewOnly = await viewOnlyChain.addBlock(newBlock) @@ -691,7 +685,7 @@ describe('Wallet', () => { }) // Create a block with a miner's fee - const minersfee2 = await strategy.createMinersFee( + const minersfee2 = await chain.createMinersFee( transaction.fee(), newBlock.header.sequence + 1, generateKey().spendingKey, @@ -768,7 +762,7 @@ describe('Wallet', () => { // Create block A2 return nodeA.chain.newBlock( [transaction], - await nodeA.strategy.createMinersFee(BigInt(0), 3, generateKey().spendingKey), + await nodeA.chain.createMinersFee(BigInt(0), 3, generateKey().spendingKey), ) }, nodeA.wallet, @@ -881,7 +875,7 @@ describe('Wallet', () => { // Create block A2 return nodeA.chain.newBlock( [transaction], - await nodeA.strategy.createMinersFee(BigInt(0), 3, generateKey().spendingKey), + await nodeA.chain.createMinersFee(BigInt(0), 3, generateKey().spendingKey), ) }, nodeB.wallet, @@ -894,7 +888,7 @@ describe('Wallet', () => { const blockB2 = await useBlockFixture(nodeB.chain, async () => nodeB.chain.newBlock( [], - await nodeB.strategy.createMinersFee(BigInt(0), 3, generateKey().spendingKey), + await nodeB.chain.createMinersFee(BigInt(0), 3, generateKey().spendingKey), ), ) addedBlock = await nodeB.chain.addBlock(blockB2) @@ -904,7 +898,7 @@ describe('Wallet', () => { const blockB3 = await useBlockFixture(nodeB.chain, async () => nodeB.chain.newBlock( [], - await nodeB.strategy.createMinersFee(BigInt(0), 4, generateKey().spendingKey), + await nodeB.chain.createMinersFee(BigInt(0), 4, generateKey().spendingKey), ), ) addedBlock = await nodeB.chain.addBlock(blockB3) @@ -1004,7 +998,7 @@ describe('Wallet', () => { const mintBlock = await node.chain.newBlock( [transaction], - await node.strategy.createMinersFee(transaction.fee(), 4, generateKey().spendingKey), + await node.chain.createMinersFee(transaction.fee(), 4, generateKey().spendingKey), ) await expect(node.chain).toAddBlock(mintBlock) await node.wallet.updateHead() @@ -1246,7 +1240,7 @@ describe('Wallet', () => { }) // Create a block with a miner's fee and the transaction to send IRON to the multisig account - const minersfee2 = await nodeTest.strategy.createMinersFee( + const minersfee2 = await nodeTest.chain.createMinersFee( transaction.fee(), block.header.sequence + 1, miner.spendingKey, @@ -1314,7 +1308,7 @@ describe('Wallet', () => { ) const frostTransaction = new Transaction(serializedFrostTransaction) - const minersfee3 = await nodeTest.strategy.createMinersFee( + const minersfee3 = await nodeTest.chain.createMinersFee( transaction.fee(), newBlock2.header.sequence + 1, miner.spendingKey, diff --git a/ironfish/src/workerPool/job.test.slow.ts b/ironfish/src/workerPool/job.test.slow.ts index 8f07ca9651..7be3b065c4 100644 --- a/ironfish/src/workerPool/job.test.slow.ts +++ b/ironfish/src/workerPool/job.test.slow.ts @@ -12,13 +12,13 @@ describe('Worker Pool', () => { const nodeTest = createNodeTest(false, { config: { nodeWorkers: 1 } }) it('createMinersFee', async () => { - const { workerPool, strategy } = nodeTest + const { workerPool, chain } = nodeTest workerPool.start() expect(workerPool.workers.length).toBe(1) expect(workerPool.completed).toBe(0) - const minersFee = await strategy.createMinersFee(BigInt(0), 0, generateKey().spendingKey) + const minersFee = await chain.createMinersFee(BigInt(0), 0, generateKey().spendingKey) expect(minersFee.serialize()).toBeInstanceOf(Buffer) expect(workerPool.completed).toBe(1) From 86b39ac0ac0d8feca008b3384c52ea21c151f804 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Tue, 30 Jan 2024 15:35:07 -0800 Subject: [PATCH 15/27] Move Strategy.miningReward to Network.miningReward (#4643) --- ironfish/src/blockchain/blockchain.ts | 6 ++- ironfish/src/consensus/verifier.test.ts | 2 +- ironfish/src/consensus/verifier.ts | 2 +- ironfish/src/networks/network.test.ts | 62 +++++++++++++++++++++++++ ironfish/src/networks/network.ts | 33 +++++++++++++ ironfish/src/node.ts | 16 +++++-- ironfish/src/strategy.test.ts | 59 ----------------------- 7 files changed, 113 insertions(+), 67 deletions(-) create mode 100644 ironfish/src/networks/network.test.ts create mode 100644 ironfish/src/networks/network.ts delete mode 100644 ironfish/src/strategy.test.ts diff --git a/ironfish/src/blockchain/blockchain.ts b/ironfish/src/blockchain/blockchain.ts index 6cc4a5afad..225dc45189 100644 --- a/ironfish/src/blockchain/blockchain.ts +++ b/ironfish/src/blockchain/blockchain.ts @@ -19,6 +19,7 @@ import { NodeEncoding } from '../merkletree/database/nodes' import { MetricsMonitor } from '../metrics' import { RollingAverage } from '../metrics/rollingAverage' import { BAN_SCORE } from '../network/peers/peer' +import { Network } from '../networks/network' import { Block, BlockSerde, @@ -66,6 +67,7 @@ export class Blockchain { config: Config blockHasher: BlockHasher workerPool: WorkerPool + network: Network readonly blockchainDb: BlockchainDB readonly notes: MerkleTree< @@ -137,6 +139,7 @@ export class Blockchain { constructor(options: { location: string + network: Network strategy: Strategy workerPool: WorkerPool logger?: Logger @@ -153,6 +156,7 @@ export class Blockchain { this.location = options.location this.strategy = options.strategy + this.network = options.network this.files = options.files this.logger = logger.withTag('blockchain') this.metrics = options.metrics || new MetricsMonitor({ logger: this.logger }) @@ -1504,7 +1508,7 @@ export class Blockchain { ): Promise { // Create a new note with value equal to the inverse of the sum of the // transaction fees and the mining reward - const reward = this.strategy.miningReward(blockSequence) + const reward = this.network.miningReward(blockSequence) const amount = totalTransactionFees + BigInt(reward) const transactionVersion = this.consensus.getActiveTransactionVersion(blockSequence) diff --git a/ironfish/src/consensus/verifier.test.ts b/ironfish/src/consensus/verifier.test.ts index a832ee7b20..070bbc3bb0 100644 --- a/ironfish/src/consensus/verifier.test.ts +++ b/ironfish/src/consensus/verifier.test.ts @@ -240,7 +240,7 @@ describe('Verifier', () => { const invalidMinersTransaction = await useFixture( () => { const key = generateKey() - const reward = nodeTest.strategy.miningReward(minersBlock.header.sequence) + const reward = nodeTest.chain.network.miningReward(minersBlock.header.sequence) const owner = key.publicAddress const minerNote1 = new NativeNote( owner, diff --git a/ironfish/src/consensus/verifier.ts b/ironfish/src/consensus/verifier.ts index 67d80c2765..0ed1fd9849 100644 --- a/ironfish/src/consensus/verifier.ts +++ b/ironfish/src/consensus/verifier.ts @@ -158,7 +158,7 @@ export class Verifier { } // minersFee should be (negative) miningReward + totalTransactionFees - const miningReward = this.chain.strategy.miningReward(block.header.sequence) + const miningReward = this.chain.network.miningReward(block.header.sequence) if (minersFeeTransaction.fee() !== -1n * (BigInt(miningReward) + totalTransactionFees)) { return { valid: false, reason: VerificationResultReason.INVALID_MINERS_FEE } diff --git a/ironfish/src/networks/network.test.ts b/ironfish/src/networks/network.test.ts new file mode 100644 index 0000000000..6d37ce82d9 --- /dev/null +++ b/ironfish/src/networks/network.test.ts @@ -0,0 +1,62 @@ +/* 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 { BlockHasher } from '../blockHasher' +import { Consensus } from '../consensus' +import { Strategy } from '../strategy' +import { FISH_HASH_CONTEXT } from '../testUtilities' +import { WorkerPool } from '../workerPool' +import { DEVNET } from './definitions' +import { Network } from './network' + +describe('Network', () => { + let network: Network + + beforeAll(() => { + const consensus = new Consensus({ + allowedBlockFutureSeconds: 15, + genesisSupplyInIron: 42000000, + targetBlockTimeInSeconds: 60, + targetBucketTimeInSeconds: 10, + maxBlockSizeBytes: 512 * 1024, + minFee: 1, + enableAssetOwnership: 1, + enforceSequentialBlockTime: 3, + enableFishHash: 'never', + }) + + const blockHasher = new BlockHasher({ consensus, context: FISH_HASH_CONTEXT }) + + const strategy = new Strategy({ + workerPool: new WorkerPool(), + consensus, + blockHasher, + }) + + network = new Network(DEVNET, strategy) + }) + + describe('miningReward', () => { + it('miners reward is properly calculated for year 0-1', () => { + // for 60 second block time, miner's block reward in the first year should be 20 IRON + const ironFishYearInBlocks = + (365 * 24 * 60 * 60) / network.definition.consensus.targetBlockTimeInSeconds + + let minersReward = network.miningReward(1) + expect(minersReward).toBe(20 * 10 ** 8) + + minersReward = network.miningReward(ironFishYearInBlocks - 1) + expect(minersReward).toBe(20 * 10 ** 8) + }) + + it('miners reward is properly calculated for year 1-2', () => { + // for 60 second block time, miner's block reward in the second year should be 19 IRON + const ironFishYearInBlocks = + (365 * 24 * 60 * 60) / network.definition.consensus.targetBlockTimeInSeconds + + const minersReward = network.miningReward(ironFishYearInBlocks + 1) + expect(minersReward).toBe(19 * 10 ** 8) + }) + }) +}) diff --git a/ironfish/src/networks/network.ts b/ironfish/src/networks/network.ts new file mode 100644 index 0000000000..2e9ddc9122 --- /dev/null +++ b/ironfish/src/networks/network.ts @@ -0,0 +1,33 @@ +/* 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 { Assert } from '../assert' +import { Strategy } from '../strategy' +import { defaultNetworkName, isDefaultNetworkId, NetworkDefinition } from './networkDefinition' + +export class Network { + readonly default: boolean + readonly name: string + readonly id: number + readonly definition: NetworkDefinition + readonly strategy: Strategy + + constructor(definition: NetworkDefinition, strategy: Strategy) { + this.id = definition.id + this.default = isDefaultNetworkId(definition.id) + this.definition = definition + this.strategy = strategy + + if (this.default) { + const defaultName = defaultNetworkName(definition.id) + Assert.isNotUndefined(defaultName) + this.name = defaultName + } else { + this.name = `Custom Network ${definition.id}` + } + } + + miningReward(sequence: number): number { + return this.strategy.miningReward(sequence) + } +} diff --git a/ironfish/src/node.ts b/ironfish/src/node.ts index 2a3a65ffcd..4bbb81c5ea 100644 --- a/ironfish/src/node.ts +++ b/ironfish/src/node.ts @@ -25,6 +25,7 @@ import { PeerNetwork, PrivateIdentity, privateIdentityToIdentity } from './netwo import { isHexSecretKey } from './network/identity' import { IsomorphicWebSocketConstructor, NodeDataChannelType } from './network/types' import { getNetworkDefinition } from './networks' +import { Network } from './networks/network' import { Package } from './package' import { Platform } from './platform' import { ALL_API_NAMESPACES, RpcMemoryClient } from './rpc' @@ -54,6 +55,7 @@ export class FullNode { pkg: Package telemetry: Telemetry assetsVerifier: AssetsVerifier + network: Network started = false shutdownPromise: Promise | null = null @@ -75,8 +77,8 @@ export class FullNode { nodeDataChannel, privateIdentity, peerStore, - networkId, assetsVerifier, + network, }: { pkg: Package files: FileSystem @@ -93,8 +95,8 @@ export class FullNode { nodeDataChannel: NodeDataChannelType privateIdentity: PrivateIdentity peerStore: PeerStore - networkId: number assetsVerifier: AssetsVerifier + network: Network }) { this.files = files this.config = config @@ -103,6 +105,7 @@ export class FullNode { this.chain = chain this.strategy = strategy this.metrics = metrics + this.network = network this.miningManager = new MiningManager({ chain, memPool, @@ -133,11 +136,11 @@ export class FullNode { { name: 'node_id', type: 'string', value: internal.get('telemetryNodeId') }, { name: 'session_id', type: 'string', value: uuid() }, ], - networkId, + networkId: network.id, }) this.peerNetwork = new PeerNetwork({ - networkId, + networkId: network.id, identity: privateIdentity, agent: Platform.getAgent(pkg), port: config.get('peerPort'), @@ -286,6 +289,8 @@ export class FullNode { strategyClass = strategyClass || Strategy const strategy = new strategyClass({ workerPool, consensus, blockHasher }) + const network = new Network(networkDefinition, strategy) + const chain = new Blockchain({ location: config.chainDatabasePath, strategy, @@ -298,6 +303,7 @@ export class FullNode { genesis: networkDefinition.genesis, config, blockHasher, + network, }) const feeEstimator = new FeeEstimator({ @@ -356,8 +362,8 @@ export class FullNode { nodeDataChannel, privateIdentity, peerStore, - networkId: networkDefinition.id, assetsVerifier, + network, }) memoryClient.router = node.rpc.getRouter(ALL_API_NAMESPACES) diff --git a/ironfish/src/strategy.test.ts b/ironfish/src/strategy.test.ts deleted file mode 100644 index 163613fc84..0000000000 --- a/ironfish/src/strategy.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* 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 { BlockHasher } from './blockHasher' -import { Consensus, ConsensusParameters } from './consensus' -import { Strategy } from './strategy' -import { FISH_HASH_CONTEXT } from './testUtilities' -import { WorkerPool } from './workerPool' - -describe('Miners reward', () => { - let strategy: Strategy - - const consensusParameters: ConsensusParameters = { - allowedBlockFutureSeconds: 15, - genesisSupplyInIron: 42000000, - targetBlockTimeInSeconds: 60, - targetBucketTimeInSeconds: 10, - maxBlockSizeBytes: 512 * 1024, - minFee: 1, - enableAssetOwnership: 1, - enforceSequentialBlockTime: 3, - enableFishHash: 'never', - } - - beforeAll(() => { - const consensus = new Consensus(consensusParameters) - const blockHasher = new BlockHasher({ consensus, context: FISH_HASH_CONTEXT }) - strategy = new Strategy({ - workerPool: new WorkerPool(), - consensus, - blockHasher, - }) - }) - - // see https://ironfish.network/docs/whitepaper/4_mining#include-the-miner-reward-based-on-coin-emission-schedule - // for more details - - // for 60 second block time, miner's block reward in the first year should be 20 IRON - it('miners reward is properly calculated for year 0-1', () => { - const ironFishYearInBlocks = - (365 * 24 * 60 * 60) / consensusParameters.targetBlockTimeInSeconds - - let minersReward = strategy.miningReward(1) - expect(minersReward).toBe(20 * 10 ** 8) - - minersReward = strategy.miningReward(ironFishYearInBlocks - 1) - expect(minersReward).toBe(20 * 10 ** 8) - }) - - // for 60 second block time, miner's block reward in the second year should be 19 IRON - it('miners reward is properly calculated for year 1-2', () => { - const ironFishYearInBlocks = - (365 * 24 * 60 * 60) / consensusParameters.targetBlockTimeInSeconds - - const minersReward = strategy.miningReward(ironFishYearInBlocks + 1) - expect(minersReward).toBe(19 * 10 ** 8) - }) -}) From 099e3e69fc327c6caedaa0c638e7656d5ca5c67a Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Tue, 30 Jan 2024 15:56:25 -0800 Subject: [PATCH 16/27] Delete Strategy (#4644) This was an old concept that is no longer needed for when Iron Fish was a library to build a crypto coin and not a crypto coin itself. The conecpts that were here have been moved off and to other models. --- ironfish/src/blockchain/blockchain.ts | 4 -- ironfish/src/index.ts | 1 - ironfish/src/networks/index.ts | 1 + ironfish/src/networks/network.test.ts | 14 +--- ironfish/src/networks/network.ts | 54 ++++++++++++-- ironfish/src/node.ts | 17 ++--- ironfish/src/rpc/routes/rpcContext.ts | 4 +- .../src/rpc/routes/wallet/addTransaction.ts | 4 +- ironfish/src/sdk.ts | 16 ++--- ironfish/src/strategy.ts | 71 ------------------- ironfish/src/testUtilities/mocks.ts | 4 -- ironfish/src/testUtilities/nodeTest.ts | 20 +++--- ironfish/src/testUtilities/routeTest.ts | 8 +-- .../{strategy.ts => testNetwork.ts} | 4 +- ironfish/src/wallet/wallet.test.ts | 4 +- 15 files changed, 85 insertions(+), 141 deletions(-) delete mode 100644 ironfish/src/strategy.ts rename ironfish/src/testUtilities/{strategy.ts => testNetwork.ts} (85%) diff --git a/ironfish/src/blockchain/blockchain.ts b/ironfish/src/blockchain/blockchain.ts index 225dc45189..83b52fb0b4 100644 --- a/ironfish/src/blockchain/blockchain.ts +++ b/ironfish/src/blockchain/blockchain.ts @@ -45,7 +45,6 @@ import { import { Target } from '../primitives/target' import { Transaction, TransactionHash } from '../primitives/transaction' import { BUFFER_ENCODING, IDatabaseTransaction } from '../storage' -import { Strategy } from '../strategy' import { AsyncUtils, BenchUtils, HashUtils } from '../utils' import { WorkerPool } from '../workerPool' import { AssetValue } from './database/assetValue' @@ -57,7 +56,6 @@ export const VERSION_DATABASE_CHAIN = 28 export class Blockchain { logger: Logger - strategy: Strategy verifier: Verifier metrics: MetricsMonitor location: string @@ -140,7 +138,6 @@ export class Blockchain { constructor(options: { location: string network: Network - strategy: Strategy workerPool: WorkerPool logger?: Logger metrics?: MetricsMonitor @@ -155,7 +152,6 @@ export class Blockchain { const logger = options.logger || createRootLogger() this.location = options.location - this.strategy = options.strategy this.network = options.network this.files = options.files this.logger = logger.withTag('blockchain') diff --git a/ironfish/src/index.ts b/ironfish/src/index.ts index 4e245686fe..1ee9e55d5b 100644 --- a/ironfish/src/index.ts +++ b/ironfish/src/index.ts @@ -18,7 +18,6 @@ export * from './migrations' export * from './node' export * from './rpc' export * from './serde' -export * from './strategy' export * from './storage' export * from './mining' export * from './metrics' diff --git a/ironfish/src/networks/index.ts b/ironfish/src/networks/index.ts index 905a0eb891..ac549e0266 100644 --- a/ironfish/src/networks/index.ts +++ b/ironfish/src/networks/index.ts @@ -4,3 +4,4 @@ export * from './networkDefinition' export * from './definitions' +export * from './network' diff --git a/ironfish/src/networks/network.test.ts b/ironfish/src/networks/network.test.ts index 6d37ce82d9..4dbe004687 100644 --- a/ironfish/src/networks/network.test.ts +++ b/ironfish/src/networks/network.test.ts @@ -2,11 +2,7 @@ * 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 { BlockHasher } from '../blockHasher' import { Consensus } from '../consensus' -import { Strategy } from '../strategy' -import { FISH_HASH_CONTEXT } from '../testUtilities' -import { WorkerPool } from '../workerPool' import { DEVNET } from './definitions' import { Network } from './network' @@ -26,15 +22,7 @@ describe('Network', () => { enableFishHash: 'never', }) - const blockHasher = new BlockHasher({ consensus, context: FISH_HASH_CONTEXT }) - - const strategy = new Strategy({ - workerPool: new WorkerPool(), - consensus, - blockHasher, - }) - - network = new Network(DEVNET, strategy) + network = new Network(DEVNET, consensus) }) describe('miningReward', () => { diff --git a/ironfish/src/networks/network.ts b/ironfish/src/networks/network.ts index 2e9ddc9122..6e7f986ed5 100644 --- a/ironfish/src/networks/network.ts +++ b/ironfish/src/networks/network.ts @@ -2,7 +2,8 @@ * 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 { Assert } from '../assert' -import { Strategy } from '../strategy' +import { Consensus } from '../consensus' +import { MathUtils } from '../utils' import { defaultNetworkName, isDefaultNetworkId, NetworkDefinition } from './networkDefinition' export class Network { @@ -10,13 +11,15 @@ export class Network { readonly name: string readonly id: number readonly definition: NetworkDefinition - readonly strategy: Strategy + readonly consensus: Consensus - constructor(definition: NetworkDefinition, strategy: Strategy) { + private miningRewardCachedByYear = new Map() + + constructor(definition: NetworkDefinition, consensus: Consensus) { this.id = definition.id this.default = isDefaultNetworkId(definition.id) this.definition = definition - this.strategy = strategy + this.consensus = consensus if (this.default) { const defaultName = defaultNetworkName(definition.id) @@ -27,7 +30,48 @@ export class Network { } } + /** + * Calculate the mining reward for a block based on its sequence + * + * See https://ironfish.network/docs/whitepaper/4_mining#include-the-miner-reward-based-on-coin-emission-schedule + * + * Annual coin issuance from mining goes down every year. Year is defined here by the + * number of blocks + * + * Given the genesis block supply (genesisSupplyInIron) the formula to calculate + * reward per block is: + * (genesisSupply / 4) * e ^(-.05 * yearsAfterLaunch) + * Where e is the natural number e (Euler's number), and -.05 is a decay function constant + * + * @param sequence Block sequence + * @returns mining reward (in ORE) per block given the block sequence + */ miningReward(sequence: number): number { - return this.strategy.miningReward(sequence) + const ironFishYearInBlocks = + (365 * 24 * 60 * 60) / this.definition.consensus.targetBlockTimeInSeconds + const yearsAfterLaunch = Math.floor(Number(sequence) / ironFishYearInBlocks) + + let reward = this.miningRewardCachedByYear.get(yearsAfterLaunch) + if (reward) { + return reward + } + + const annualReward = + (this.definition.consensus.genesisSupplyInIron / 4) * Math.E ** (-0.05 * yearsAfterLaunch) + + // This rounds and produces an incorrect result but must + // be kept because it would cause a hard fork once you reach + // a floating point reward. This should have used the logic + // in CurrencyUtils.decodeIron. This entire function should + // have only done this math in ore amounts. + function convertIronToOre(iron: number): number { + return Math.round(iron * 10 ** 8) + } + + reward = convertIronToOre(MathUtils.roundBy(annualReward / ironFishYearInBlocks, 0.125)) + + this.miningRewardCachedByYear.set(yearsAfterLaunch, reward) + + return reward } } diff --git a/ironfish/src/node.ts b/ironfish/src/node.ts index 4bbb81c5ea..757775693a 100644 --- a/ironfish/src/node.ts +++ b/ironfish/src/node.ts @@ -30,7 +30,6 @@ import { Package } from './package' import { Platform } from './platform' import { ALL_API_NAMESPACES, RpcMemoryClient } from './rpc' import { RpcServer } from './rpc/server' -import { Strategy } from './strategy' import { Syncer } from './syncer' import { Telemetry } from './telemetry/telemetry' import { Wallet, WalletDB } from './wallet' @@ -38,7 +37,6 @@ import { calculateWorkers, WorkerPool } from './workerPool' export class FullNode { chain: Blockchain - strategy: Strategy config: Config internal: InternalStore wallet: Wallet @@ -68,7 +66,6 @@ export class FullNode { config, internal, wallet, - strategy, metrics, memPool, workerPool, @@ -86,7 +83,6 @@ export class FullNode { internal: InternalStore wallet: Wallet chain: Blockchain - strategy: Strategy metrics: MetricsMonitor memPool: MemPool workerPool: WorkerPool @@ -103,7 +99,6 @@ export class FullNode { this.internal = internal this.wallet = wallet this.chain = chain - this.strategy = strategy this.metrics = metrics this.network = network this.miningManager = new MiningManager({ @@ -196,7 +191,7 @@ export class FullNode { logger = createRootLogger(), metrics, files, - strategyClass, + networkClass, webSocket, privateIdentity, fishHashContext, @@ -211,7 +206,7 @@ export class FullNode { logger?: Logger metrics?: MetricsMonitor files: FileSystem - strategyClass: typeof Strategy | null + networkClass: typeof Network | null webSocket: IsomorphicWebSocketConstructor privateIdentity?: PrivateIdentity fishHashContext?: FishHashContext @@ -286,14 +281,11 @@ export class FullNode { context: fishHashContext, }) - strategyClass = strategyClass || Strategy - const strategy = new strategyClass({ workerPool, consensus, blockHasher }) - - const network = new Network(networkDefinition, strategy) + networkClass = networkClass || Network + const network = new networkClass(networkDefinition, consensus) const chain = new Blockchain({ location: config.chainDatabasePath, - strategy, logger, metrics, autoSeed, @@ -349,7 +341,6 @@ export class FullNode { const node = new FullNode({ pkg, chain, - strategy, files, config, internal, diff --git a/ironfish/src/rpc/routes/rpcContext.ts b/ironfish/src/rpc/routes/rpcContext.ts index b2881e43f4..9d7cca1299 100644 --- a/ironfish/src/rpc/routes/rpcContext.ts +++ b/ironfish/src/rpc/routes/rpcContext.ts @@ -6,7 +6,7 @@ import { AssetsVerifier } from '../../assets' import { Config, InternalStore } from '../../fileStores' import { FileSystem } from '../../fileSystems' import { Logger } from '../../logger' -import { Strategy } from '../../strategy' +import { Network } from '../../networks' import { Wallet } from '../../wallet' import { WorkerPool } from '../../workerPool' import { RpcRequest } from '../request' @@ -21,7 +21,7 @@ export type RpcContext = Partial<{ logger: Logger rpc: RpcServer assetsVerifier: AssetsVerifier - strategy: Strategy + network: Network shutdown: () => Promise }> diff --git a/ironfish/src/rpc/routes/wallet/addTransaction.ts b/ironfish/src/rpc/routes/wallet/addTransaction.ts index fa377ca439..7c9a3301c6 100644 --- a/ironfish/src/rpc/routes/wallet/addTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/addTransaction.ts @@ -40,12 +40,12 @@ routes.register( `${ApiNamespace.wallet}/addTransaction`, AddTransactionRequestSchema, async (request, context): Promise => { - AssertHasRpcContext(request, context, 'strategy', 'wallet') + AssertHasRpcContext(request, context, 'network', 'wallet') const data = Buffer.from(request.data.transaction, 'hex') const transaction = new Transaction(data) - const verify = Verifier.verifyCreatedTransaction(transaction, context.strategy.consensus) + const verify = Verifier.verifyCreatedTransaction(transaction, context.network.consensus) if (!verify.valid) { throw new RpcValidationError(`Invalid transaction, reason: ${String(verify.reason)}`, 400) diff --git a/ironfish/src/sdk.ts b/ironfish/src/sdk.ts index bec00e1738..1ff35072d8 100644 --- a/ironfish/src/sdk.ts +++ b/ironfish/src/sdk.ts @@ -23,6 +23,7 @@ import { MetricsMonitor } from './metrics' import { isHexSecretKey, PrivateIdentity } from './network/identity' import { IsomorphicWebSocketConstructor } from './network/types' import { WebSocketClient } from './network/webSocketClient' +import { Network } from './networks' import { FullNode } from './node' import { IronfishPKG, Package } from './package' import { Platform } from './platform' @@ -35,7 +36,6 @@ import { RpcMemoryClient } from './rpc/clients/memoryClient' import { RpcTcpClient } from './rpc/clients/tcpClient' import { RpcTlsClient } from './rpc/clients/tlsClient' import { ALL_API_NAMESPACES } from './rpc/routes/router' -import { Strategy } from './strategy' import { NodeUtils } from './utils' export class IronfishSdk { @@ -46,7 +46,7 @@ export class IronfishSdk { logger: Logger metrics: MetricsMonitor internal: InternalStore - strategyClass: typeof Strategy | null + networkClass: typeof Network | null dataDir: string private constructor( @@ -57,7 +57,7 @@ export class IronfishSdk { fileSystem: FileSystem, logger: Logger, metrics: MetricsMonitor, - strategyClass: typeof Strategy | null = null, + networkClass: typeof Network | null = null, dataDir: string, ) { this.pkg = pkg @@ -67,7 +67,7 @@ export class IronfishSdk { this.fileSystem = fileSystem this.logger = logger this.metrics = metrics - this.strategyClass = strategyClass + this.networkClass = networkClass this.dataDir = dataDir } @@ -80,7 +80,7 @@ export class IronfishSdk { dataDir, logger = createRootLogger(), metrics, - strategyClass, + networkClass, }: { pkg?: Package configName?: string @@ -90,7 +90,7 @@ export class IronfishSdk { dataDir?: string logger?: Logger metrics?: MetricsMonitor - strategyClass?: typeof Strategy + networkClass?: typeof Network } = {}): Promise { const runtime = Platform.getRuntime() @@ -173,7 +173,7 @@ export class IronfishSdk { fileSystem, logger, metrics, - strategyClass, + networkClass, dataDir, ) } @@ -200,7 +200,7 @@ export class IronfishSdk { autoSeed: autoSeed, logger: this.logger, metrics: this.metrics, - strategyClass: this.strategyClass, + networkClass: this.networkClass, webSocket: webSocket, privateIdentity: privateIdentity, dataDir: this.dataDir, diff --git a/ironfish/src/strategy.ts b/ironfish/src/strategy.ts deleted file mode 100644 index d5d35df711..0000000000 --- a/ironfish/src/strategy.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* 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 { BlockHasher } from './blockHasher' -import { Consensus } from './consensus' -import { MathUtils } from './utils' -import { WorkerPool } from './workerPool' - -/** - * Implementation of a Blockchain Strategy using zero-knowledge proofs. - */ -export class Strategy { - readonly workerPool: WorkerPool - readonly consensus: Consensus - readonly blockHasher: BlockHasher - - private miningRewardCachedByYear: Map - - constructor(options: { - workerPool: WorkerPool - consensus: Consensus - blockHasher: BlockHasher - }) { - this.miningRewardCachedByYear = new Map() - this.workerPool = options.workerPool - this.consensus = options.consensus - this.blockHasher = options.blockHasher - } - - /** - * Calculate the mining reward for a block based on its sequence - * - * See https://ironfish.network/docs/whitepaper/4_mining#include-the-miner-reward-based-on-coin-emission-schedule - * - * Annual coin issuance from mining goes down every year. Year is defined here by the - * number of blocks - * - * Given the genesis block supply (genesisSupplyInIron) the formula to calculate - * reward per block is: - * (genesisSupply / 4) * e ^(-.05 * yearsAfterLaunch) - * Where e is the natural number e (Euler's number), and -.05 is a decay function constant - * - * @param sequence Block sequence - * @returns mining reward (in ORE) per block given the block sequence - */ - miningReward(sequence: number): number { - const ironFishYearInBlocks = - (365 * 24 * 60 * 60) / this.consensus.parameters.targetBlockTimeInSeconds - const yearsAfterLaunch = Math.floor(Number(sequence) / ironFishYearInBlocks) - - let reward = this.miningRewardCachedByYear.get(yearsAfterLaunch) - if (reward) { - return reward - } - - const annualReward = - (this.consensus.parameters.genesisSupplyInIron / 4) * Math.E ** (-0.05 * yearsAfterLaunch) - - reward = this.convertIronToOre( - MathUtils.roundBy(annualReward / ironFishYearInBlocks, 0.125), - ) - - this.miningRewardCachedByYear.set(yearsAfterLaunch, reward) - - return reward - } - - convertIronToOre(iron: number): number { - return Math.round(iron * 10 ** 8) - } -} diff --git a/ironfish/src/testUtilities/mocks.ts b/ironfish/src/testUtilities/mocks.ts index 72146712a8..a39ea31c6d 100644 --- a/ironfish/src/testUtilities/mocks.ts +++ b/ironfish/src/testUtilities/mocks.ts @@ -46,10 +46,6 @@ export function mockChain(): any { } } -export function mockStrategy(): any { - return {} -} - export function mockNode(): any { return { wallet: mockWallet(), diff --git a/ironfish/src/testUtilities/nodeTest.ts b/ironfish/src/testUtilities/nodeTest.ts index 571e1883c9..6daf8e2ae2 100644 --- a/ironfish/src/testUtilities/nodeTest.ts +++ b/ironfish/src/testUtilities/nodeTest.ts @@ -12,7 +12,7 @@ import { IronfishSdk } from '../sdk' import { Syncer } from '../syncer' import { Wallet } from '../wallet' import { WorkerPool } from '../workerPool' -import { TestStrategy } from './strategy' +import { TestNetwork } from './testNetwork' import { getUniqueTestDataDir } from './utils' export type NodeTestOptions = @@ -35,7 +35,7 @@ export class NodeTest { sdk!: IronfishSdk node!: FullNode - strategy!: TestStrategy + network!: TestNetwork verifier!: Verifier chain!: Blockchain wallet!: Wallet @@ -46,7 +46,7 @@ export class NodeTest { setups = new Array<{ sdk: IronfishSdk node: FullNode - strategy: TestStrategy + network: TestNetwork chain: Blockchain wallet: Wallet peerNetwork: PeerNetwork @@ -61,7 +61,7 @@ export class NodeTest { async createSetup(options?: NodeTestOptions): Promise<{ sdk: IronfishSdk node: FullNode - strategy: TestStrategy + network: TestNetwork verifier: Verifier chain: Blockchain wallet: Wallet @@ -74,9 +74,9 @@ export class NodeTest { } const dataDir = getUniqueTestDataDir() - const strategyClass = TestStrategy + const networkClass = TestNetwork - const sdk = await IronfishSdk.init({ dataDir, strategyClass }) + const sdk = await IronfishSdk.init({ dataDir, networkClass }) sdk.config.setOverride('bootstrapNodes', ['']) sdk.config.setOverride('enableListenP2P', false) @@ -99,7 +99,7 @@ export class NodeTest { networkId: 2, }) - const strategy = node.strategy as TestStrategy + const network = node.network as TestNetwork const chain = node.chain const wallet = node.wallet const peerNetwork = node.peerNetwork @@ -114,7 +114,7 @@ export class NodeTest { const setup = { sdk, node, - strategy, + network, verifier, chain, wallet, @@ -128,12 +128,12 @@ export class NodeTest { } async setup(): Promise { - const { sdk, node, strategy, verifier, chain, wallet, peerNetwork, syncer, workerPool } = + const { sdk, node, network, verifier, chain, wallet, peerNetwork, syncer, workerPool } = await this.createSetup() this.sdk = sdk this.node = node - this.strategy = strategy + this.network = network this.verifier = verifier this.chain = chain this.wallet = wallet diff --git a/ironfish/src/testUtilities/routeTest.ts b/ironfish/src/testUtilities/routeTest.ts index 01835bef44..17afcaa372 100644 --- a/ironfish/src/testUtilities/routeTest.ts +++ b/ironfish/src/testUtilities/routeTest.ts @@ -14,7 +14,7 @@ import { Syncer } from '../syncer' import { Wallet } from '../wallet' import { WorkerPool } from '../workerPool' import { NodeTest } from './nodeTest' -import { TestStrategy } from './strategy' +import { TestNetwork } from './testNetwork' /** * Used as an easy wrapper for an RPC route test. Use {@link createRouteTest} @@ -28,7 +28,7 @@ export class RouteTest extends NodeTest { async createSetup(): Promise<{ sdk: IronfishSdk node: FullNode - strategy: TestStrategy + network: TestNetwork verifier: Verifier chain: Blockchain wallet: Wallet @@ -46,12 +46,12 @@ export class RouteTest extends NodeTest { } async setup(): Promise { - const { sdk, node, strategy, chain, wallet, peerNetwork, syncer, workerPool, client } = + const { sdk, node, network, chain, wallet, peerNetwork, syncer, workerPool, client } = await this.createSetup() this.sdk = sdk this.node = node - this.strategy = strategy + this.network = network this.chain = chain this.wallet = wallet this.syncer = syncer diff --git a/ironfish/src/testUtilities/strategy.ts b/ironfish/src/testUtilities/testNetwork.ts similarity index 85% rename from ironfish/src/testUtilities/strategy.ts rename to ironfish/src/testUtilities/testNetwork.ts index 31a04ecd59..265d5099b6 100644 --- a/ironfish/src/testUtilities/strategy.ts +++ b/ironfish/src/testUtilities/testNetwork.ts @@ -2,9 +2,9 @@ * 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 { Strategy } from '../strategy' +import { Network } from '../networks' -export class TestStrategy extends Strategy { +export class TestNetwork extends Network { private _miningReward: number | null = null disableMiningReward(): void { diff --git a/ironfish/src/wallet/wallet.test.ts b/ironfish/src/wallet/wallet.test.ts index 65e1ee20de..80da770905 100644 --- a/ironfish/src/wallet/wallet.test.ts +++ b/ironfish/src/wallet/wallet.test.ts @@ -28,8 +28,8 @@ describe('Wallet', () => { const nodeTest = createNodeTest() it('should throw an error when chain processor head does not exist in chain', async () => { - const { node, strategy } = nodeTest - strategy.disableMiningReward() + const { node, network } = nodeTest + network.disableMiningReward() node.wallet['chainProcessor'].hash = Buffer.from('0') From 4fef85385d49153be3906a388f6f373783508c78 Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Tue, 30 Jan 2024 16:39:15 -0800 Subject: [PATCH 17/27] Add FishHash to solo miner (#4639) --- ironfish-cli/src/commands/miners/start.ts | 8 ++- ironfish/src/fileStores/config.ts | 2 +- ironfish/src/mining/soloMiner.ts | 82 ++++++++++++----------- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/ironfish-cli/src/commands/miners/start.ts b/ironfish-cli/src/commands/miners/start.ts index 15508ae4fd..5f38474c4b 100644 --- a/ironfish-cli/src/commands/miners/start.ts +++ b/ironfish-cli/src/commands/miners/start.ts @@ -27,7 +27,7 @@ export class Miner extends IronfishCommand { ...RemoteFlags, threads: Flags.integer({ char: 't', - default: -1, + default: 1, description: 'Number of CPU threads to use for mining. -1 will auto-detect based on number of CPU cores.', }), @@ -52,6 +52,11 @@ export class Miner extends IronfishCommand { description: 'Connect to pool over tls', allowNo: true, }), + fishHashFull: Flags.boolean({ + description: 'Instantiate the full 4.6GB fish hash context in every thread', + default: false, + allowNo: true, + }), } async start(): Promise { @@ -145,6 +150,7 @@ export class Miner extends IronfishCommand { logger: this.logger, batchSize, rpc, + fishHashFullContext: flags.fishHashFull, }) miner.start() diff --git a/ironfish/src/fileStores/config.ts b/ironfish/src/fileStores/config.ts index a639263d04..c662c42b81 100644 --- a/ironfish/src/fileStores/config.ts +++ b/ironfish/src/fileStores/config.ts @@ -453,7 +453,7 @@ export class Config< assetVerificationApi: '', generateNewIdentity: false, blocksPerMessage: 25, - minerBatchSize: 25000, + minerBatchSize: 1000, preemptiveBlockMining: true, poolName: 'Iron Fish Pool', poolAccountName: undefined, diff --git a/ironfish/src/mining/soloMiner.ts b/ironfish/src/mining/soloMiner.ts index 9c5ccd9177..71dabd2b53 100644 --- a/ironfish/src/mining/soloMiner.ts +++ b/ironfish/src/mining/soloMiner.ts @@ -2,20 +2,19 @@ * 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 { ThreadPoolHandler } from '@ironfish/rust-nodejs' -import { blake3 } from '@napi-rs/blake-hash' import { Assert } from '../assert' -import { ConsensusParameters } from '../consensus' +import { serializeHeaderBlake3, serializeHeaderFishHash } from '../blockHasher' +import { Consensus } from '../consensus' import { Logger } from '../logger' import { Meter } from '../metrics/meter' import { Target } from '../primitives/target' import { RpcSocketClient } from '../rpc/clients/socketClient' -import { SerializedBlockTemplate } from '../serde/BlockTemplateSerde' +import { RawBlockTemplateSerde, SerializedBlockTemplate } from '../serde/BlockTemplateSerde' import { BigIntUtils } from '../utils/bigint' import { ErrorUtils } from '../utils/error' import { FileUtils } from '../utils/file' import { PromiseUtils } from '../utils/promise' import { SetTimeoutToken } from '../utils/types' -import { MINEABLE_BLOCK_HEADER_GRAFFITI_OFFSET, mineableHeaderString } from './utils' const RECALCULATE_TARGET_TIMEOUT = 10000 @@ -36,7 +35,7 @@ export class MiningSoloMiner { private miningRequestBlocks: Map private miningRequestId: number - private consensusParameters: ConsensusParameters | null = null + private consensus: Consensus | null = null private currentHeadTimestamp: number | null private currentHeadDifficulty: bigint | null @@ -52,13 +51,20 @@ export class MiningSoloMiner { logger: Logger graffiti: Buffer rpc: RpcSocketClient + fishHashFullContext: boolean }) { this.rpc = options.rpc this.logger = options.logger this.graffiti = options.graffiti const threadCount = options.threadCount ?? 1 - this.threadPool = new ThreadPoolHandler(threadCount, options.batchSize, true, false, false) + this.threadPool = new ThreadPoolHandler( + threadCount, + options.batchSize, + true, + true, + options.fishHashFullContext, + ) this.miningRequestId = 0 this.nextMiningRequestId = 0 @@ -124,20 +130,6 @@ export class MiningSoloMiner { await this.stopPromise } - newWork(miningRequestId: number, header: Buffer): void { - this.logger.debug( - `new work ${this.target.toString('hex')}, ${miningRequestId} ${FileUtils.formatHashRate( - this.hashRate.rate1s, - )}/s`, - ) - - const headerBytes = Buffer.concat([header]) - headerBytes.set(this.graffiti, MINEABLE_BLOCK_HEADER_GRAFFITI_OFFSET) - - this.waiting = false - this.threadPool.newWork(headerBytes, this.target, miningRequestId, false) - } - waitForWork(): void { this.waiting = true this.threadPool.pause() @@ -151,7 +143,7 @@ export class MiningSoloMiner { } private async processNewBlocks() { - Assert.isNotNull(this.consensusParameters) + Assert.isNotNull(this.consensus) for await (const payload of this.rpc.miner.blockTemplateStream().contentStream()) { Assert.isNotUndefined(payload.previousBlockInfo) @@ -161,8 +153,8 @@ export class MiningSoloMiner { this.currentHeadTimestamp = payload.previousBlockInfo.timestamp this.restartCalculateTargetInterval( - this.consensusParameters.targetBlockTimeInSeconds, - this.consensusParameters.targetBucketTimeInSeconds, + this.consensus.parameters.targetBlockTimeInSeconds, + this.consensus.parameters.targetBucketTimeInSeconds, ) this.startNewWork(payload) } @@ -171,6 +163,7 @@ export class MiningSoloMiner { private startNewWork(block: SerializedBlockTemplate) { Assert.isNotNull(this.currentHeadTimestamp) Assert.isNotNull(this.currentHeadDifficulty) + Assert.isNotNull(this.consensus) const miningRequestId = this.nextMiningRequestId++ this.miningRequestBlocks.set(miningRequestId, block) @@ -178,8 +171,22 @@ export class MiningSoloMiner { this.target = Buffer.from(block.header.target, 'hex') - const work = mineableHeaderString(block.header) - this.newWork(miningRequestId, work) + const rawBlock = RawBlockTemplateSerde.deserialize(block) + rawBlock.header.graffiti = this.graffiti + + const fishHashBlock = this.consensus.isActive('enableFishHash', rawBlock.header.sequence) + const headerBytes = fishHashBlock + ? serializeHeaderFishHash(rawBlock.header) + : serializeHeaderBlake3(rawBlock.header) + + this.logger.debug( + `new work ${this.target.toString('hex')}, ${miningRequestId} ${FileUtils.formatHashRate( + this.hashRate.rate1s, + )}/s`, + ) + + this.waiting = false + this.threadPool.newWork(headerBytes, this.target, miningRequestId, fishHashBlock) } private async mine(): Promise { @@ -218,21 +225,16 @@ export class MiningSoloMiner { blockTemplate.header.graffiti = graffiti.toString('hex') blockTemplate.header.randomness = randomness - const headerBytes = mineableHeaderString(blockTemplate.header) - const hashedHeader = blake3(headerBytes) - - if (hashedHeader.compare(Buffer.from(blockTemplate.header.target, 'hex')) !== 1) { - this.logger.debug('Valid block, submitting to node') + this.logger.debug('Valid block, submitting to node') - const result = await this.rpc.miner.submitBlock(blockTemplate) + const result = await this.rpc.miner.submitBlock(blockTemplate) - if (result.content.added) { - this.logger.info( - `Block submitted successfully! ${FileUtils.formatHashRate(this.hashRate.rate1s)}/s`, - ) - } else { - this.logger.info(`Block was rejected: ${result.content.reason}`) - } + if (result.content.added) { + this.logger.info( + `Block submitted successfully! ${FileUtils.formatHashRate(this.hashRate.rate1s)}/s`, + ) + } else { + this.logger.info(`Block was rejected: ${result.content.reason}`) } } @@ -254,12 +256,12 @@ export class MiningSoloMiner { } const consensusResponse = (await this.rpc.chain.getConsensusParameters()).content - this.consensusParameters = { + this.consensus = new Consensus({ ...consensusResponse, enableFishHash: consensusResponse.enableFishHash || 'never', enableAssetOwnership: consensusResponse.enableAssetOwnership || 'never', enforceSequentialBlockTime: consensusResponse.enforceSequentialBlockTime || 'never', - } + }) this.connectWarned = false this.logger.info('Successfully connected to node') From 1c288bb4350e202ccd24b069aa96b4b2a6e0c14b Mon Sep 17 00:00:00 2001 From: jowparks Date: Tue, 30 Jan 2024 16:48:37 -0800 Subject: [PATCH 18/27] adds create signing share rpc route (#4630) --- .../rpc/routes/multisig/createSigningShare.ts | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 ironfish/src/rpc/routes/multisig/createSigningShare.ts diff --git a/ironfish/src/rpc/routes/multisig/createSigningShare.ts b/ironfish/src/rpc/routes/multisig/createSigningShare.ts new file mode 100644 index 0000000000..bac231c380 --- /dev/null +++ b/ironfish/src/rpc/routes/multisig/createSigningShare.ts @@ -0,0 +1,55 @@ +/* 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 { roundTwo, UnsignedTransaction } from '@ironfish/rust-nodejs' +import * as yup from 'yup' +import { ApiNamespace } from '../namespaces' +import { routes } from '../router' + +export type CreateSigningShareRequest = { + signingPackage: string + keyPackage: string + unsignedTransaction: string + seed: number // TODO: remove when we have deterministic nonces +} + +export type CreateSigningCommitmentResponse = { + signingShare: string +} + +export const CreateSigningCommitmentRequestSchema: yup.ObjectSchema = + yup + .object({ + signingPackage: yup.string().defined(), + keyPackage: yup.string().defined(), + unsignedTransaction: yup.string().defined(), + seed: yup.number().defined(), + }) + .defined() + +export const CreateSigningCommitmentResponseSchema: yup.ObjectSchema = + yup + .object({ + signingShare: yup.string().defined(), + }) + .defined() + +routes.register( + `${ApiNamespace.multisig}/createSigningShare`, + CreateSigningCommitmentRequestSchema, + (request, _context): void => { + const unsigned = new UnsignedTransaction( + Buffer.from(request.data.unsignedTransaction, 'hex'), + ) + const result = roundTwo( + request.data.signingPackage, + request.data.keyPackage, + unsigned.publicKeyRandomness(), + request.data.seed, + ) + + request.end({ + signingShare: result, + }) + }, +) From c8a825d506b5c92e304498d0d890f5bab0ee7570 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Tue, 30 Jan 2024 16:56:22 -0800 Subject: [PATCH 19/27] Delete networkClass SDK argument (#4645) This was used to customize the class that the Network was instantiated with. This is no longer necesary as we don't disable the miners reward calculation anywhere in any tests. --- ironfish/src/node.ts | 5 +---- ironfish/src/sdk.ts | 8 -------- ironfish/src/testUtilities/nodeTest.ts | 13 ++++++------- ironfish/src/testUtilities/routeTest.ts | 4 ++-- ironfish/src/testUtilities/testNetwork.ts | 21 --------------------- ironfish/src/wallet/wallet.test.ts | 7 ++----- 6 files changed, 11 insertions(+), 47 deletions(-) delete mode 100644 ironfish/src/testUtilities/testNetwork.ts diff --git a/ironfish/src/node.ts b/ironfish/src/node.ts index 757775693a..a235018b28 100644 --- a/ironfish/src/node.ts +++ b/ironfish/src/node.ts @@ -191,7 +191,6 @@ export class FullNode { logger = createRootLogger(), metrics, files, - networkClass, webSocket, privateIdentity, fishHashContext, @@ -206,7 +205,6 @@ export class FullNode { logger?: Logger metrics?: MetricsMonitor files: FileSystem - networkClass: typeof Network | null webSocket: IsomorphicWebSocketConstructor privateIdentity?: PrivateIdentity fishHashContext?: FishHashContext @@ -281,8 +279,7 @@ export class FullNode { context: fishHashContext, }) - networkClass = networkClass || Network - const network = new networkClass(networkDefinition, consensus) + const network = new Network(networkDefinition, consensus) const chain = new Blockchain({ location: config.chainDatabasePath, diff --git a/ironfish/src/sdk.ts b/ironfish/src/sdk.ts index 1ff35072d8..8f4cece527 100644 --- a/ironfish/src/sdk.ts +++ b/ironfish/src/sdk.ts @@ -23,7 +23,6 @@ import { MetricsMonitor } from './metrics' import { isHexSecretKey, PrivateIdentity } from './network/identity' import { IsomorphicWebSocketConstructor } from './network/types' import { WebSocketClient } from './network/webSocketClient' -import { Network } from './networks' import { FullNode } from './node' import { IronfishPKG, Package } from './package' import { Platform } from './platform' @@ -46,7 +45,6 @@ export class IronfishSdk { logger: Logger metrics: MetricsMonitor internal: InternalStore - networkClass: typeof Network | null dataDir: string private constructor( @@ -57,7 +55,6 @@ export class IronfishSdk { fileSystem: FileSystem, logger: Logger, metrics: MetricsMonitor, - networkClass: typeof Network | null = null, dataDir: string, ) { this.pkg = pkg @@ -67,7 +64,6 @@ export class IronfishSdk { this.fileSystem = fileSystem this.logger = logger this.metrics = metrics - this.networkClass = networkClass this.dataDir = dataDir } @@ -80,7 +76,6 @@ export class IronfishSdk { dataDir, logger = createRootLogger(), metrics, - networkClass, }: { pkg?: Package configName?: string @@ -90,7 +85,6 @@ export class IronfishSdk { dataDir?: string logger?: Logger metrics?: MetricsMonitor - networkClass?: typeof Network } = {}): Promise { const runtime = Platform.getRuntime() @@ -173,7 +167,6 @@ export class IronfishSdk { fileSystem, logger, metrics, - networkClass, dataDir, ) } @@ -200,7 +193,6 @@ export class IronfishSdk { autoSeed: autoSeed, logger: this.logger, metrics: this.metrics, - networkClass: this.networkClass, webSocket: webSocket, privateIdentity: privateIdentity, dataDir: this.dataDir, diff --git a/ironfish/src/testUtilities/nodeTest.ts b/ironfish/src/testUtilities/nodeTest.ts index 6daf8e2ae2..57808771f8 100644 --- a/ironfish/src/testUtilities/nodeTest.ts +++ b/ironfish/src/testUtilities/nodeTest.ts @@ -7,12 +7,12 @@ import { Blockchain } from '../blockchain' import { Verifier } from '../consensus/verifier' import { ConfigOptions } from '../fileStores/config' import { PeerNetwork } from '../network' +import { Network } from '../networks' import { FullNode } from '../node' import { IronfishSdk } from '../sdk' import { Syncer } from '../syncer' import { Wallet } from '../wallet' import { WorkerPool } from '../workerPool' -import { TestNetwork } from './testNetwork' import { getUniqueTestDataDir } from './utils' export type NodeTestOptions = @@ -35,7 +35,7 @@ export class NodeTest { sdk!: IronfishSdk node!: FullNode - network!: TestNetwork + network!: Network verifier!: Verifier chain!: Blockchain wallet!: Wallet @@ -46,7 +46,7 @@ export class NodeTest { setups = new Array<{ sdk: IronfishSdk node: FullNode - network: TestNetwork + network: Network chain: Blockchain wallet: Wallet peerNetwork: PeerNetwork @@ -61,7 +61,7 @@ export class NodeTest { async createSetup(options?: NodeTestOptions): Promise<{ sdk: IronfishSdk node: FullNode - network: TestNetwork + network: Network verifier: Verifier chain: Blockchain wallet: Wallet @@ -74,9 +74,8 @@ export class NodeTest { } const dataDir = getUniqueTestDataDir() - const networkClass = TestNetwork - const sdk = await IronfishSdk.init({ dataDir, networkClass }) + const sdk = await IronfishSdk.init({ dataDir }) sdk.config.setOverride('bootstrapNodes', ['']) sdk.config.setOverride('enableListenP2P', false) @@ -99,7 +98,7 @@ export class NodeTest { networkId: 2, }) - const network = node.network as TestNetwork + const network = node.network const chain = node.chain const wallet = node.wallet const peerNetwork = node.peerNetwork diff --git a/ironfish/src/testUtilities/routeTest.ts b/ironfish/src/testUtilities/routeTest.ts index 17afcaa372..bd0cab1bc6 100644 --- a/ironfish/src/testUtilities/routeTest.ts +++ b/ironfish/src/testUtilities/routeTest.ts @@ -5,6 +5,7 @@ import { Blockchain } from '../blockchain' import { Verifier } from '../consensus' import { createRootLogger } from '../logger' import { PeerNetwork } from '../network/peerNetwork' +import { Network } from '../networks' import { FullNode } from '../node' import { ALL_API_NAMESPACES } from '../rpc' import { RpcMemoryAdapter } from '../rpc/adapters' @@ -14,7 +15,6 @@ import { Syncer } from '../syncer' import { Wallet } from '../wallet' import { WorkerPool } from '../workerPool' import { NodeTest } from './nodeTest' -import { TestNetwork } from './testNetwork' /** * Used as an easy wrapper for an RPC route test. Use {@link createRouteTest} @@ -28,7 +28,7 @@ export class RouteTest extends NodeTest { async createSetup(): Promise<{ sdk: IronfishSdk node: FullNode - network: TestNetwork + network: Network verifier: Verifier chain: Blockchain wallet: Wallet diff --git a/ironfish/src/testUtilities/testNetwork.ts b/ironfish/src/testUtilities/testNetwork.ts deleted file mode 100644 index 265d5099b6..0000000000 --- a/ironfish/src/testUtilities/testNetwork.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* 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 { Network } from '../networks' - -export class TestNetwork extends Network { - private _miningReward: number | null = null - - disableMiningReward(): void { - this._miningReward = 0 - } - - miningReward(sequence: number): number { - if (this._miningReward !== null) { - return this._miningReward - } - - return super.miningReward(sequence) - } -} diff --git a/ironfish/src/wallet/wallet.test.ts b/ironfish/src/wallet/wallet.test.ts index 80da770905..fad2a9351e 100644 --- a/ironfish/src/wallet/wallet.test.ts +++ b/ironfish/src/wallet/wallet.test.ts @@ -28,12 +28,9 @@ describe('Wallet', () => { const nodeTest = createNodeTest() it('should throw an error when chain processor head does not exist in chain', async () => { - const { node, network } = nodeTest - network.disableMiningReward() + nodeTest.wallet['chainProcessor'].hash = Buffer.from('0') - node.wallet['chainProcessor'].hash = Buffer.from('0') - - await expect(node.wallet.start()).rejects.toThrow() + await expect(nodeTest.wallet.start()).rejects.toThrow() }) it('should handle transaction created on fork', async () => { From 92b1516e5aa510a31b956d76d663433603714ba2 Mon Sep 17 00:00:00 2001 From: jowparks Date: Tue, 30 Jan 2024 17:12:36 -0800 Subject: [PATCH 20/27] chore: refactor naming of roundOne/roundTwo to createSigningCommitment/createSigningShare (#4648) --- ironfish-rust-nodejs/index.d.ts | 4 ++-- ironfish-rust-nodejs/index.js | 6 +++--- ironfish-rust-nodejs/src/frost.rs | 16 ++++++++++------ ironfish-rust/src/frost_utils/mod.rs | 4 ++-- .../{round_one.rs => signing_commitment.rs} | 9 ++++++--- .../{round_two.rs => signing_share.rs} | 2 +- ironfish-rust/src/transaction/tests.rs | 8 +++++--- .../routes/multisig/createSigningCommitment.ts | 4 ++-- .../rpc/routes/multisig/createSigningShare.ts | 4 ++-- ironfish/src/wallet/wallet.test.slow.ts | 16 ++++++++-------- 10 files changed, 41 insertions(+), 32 deletions(-) rename ironfish-rust/src/frost_utils/{round_one.rs => signing_commitment.rs} (84%) rename ironfish-rust/src/frost_utils/{round_two.rs => signing_share.rs} (97%) diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index f54cb713ef..f413e7f7f4 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -11,8 +11,8 @@ export interface IdentifierCommitment { identifier: string commitment: Commitment } -export function roundOne(keyPackage: string, seed: number): Commitment -export function roundTwo(signingPackage: string, keyPackage: string, publicKeyRandomness: string, seed: number): string +export function createSigningCommitment(keyPackage: string, seed: number): Commitment +export function createSigningShare(signingPackage: string, keyPackage: string, publicKeyRandomness: string, seed: number): string export function splitSecret(coordinatorSaplingKey: string, minSigners: number, maxSigners: number, identifiers: Array): TrustedDealerKeyPackages export function contribute(inputPath: string, outputPath: string, seed?: string | undefined | null): Promise export function verifyTransform(paramsPath: string, newParamsPath: string): Promise diff --git a/ironfish-rust-nodejs/index.js b/ironfish-rust-nodejs/index.js index a0594d7d55..526169e3ec 100644 --- a/ironfish-rust-nodejs/index.js +++ b/ironfish-rust-nodejs/index.js @@ -252,11 +252,11 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { FishHashContext, roundOne, roundTwo, ParticipantSecret, ParticipantIdentity, splitSecret, contribute, verifyTransform, KEY_LENGTH, NONCE_LENGTH, BoxKeyPair, randomBytes, boxMessage, unboxMessage, RollingFilter, initSignalHandler, triggerSegfault, ASSET_ID_LENGTH, ASSET_METADATA_LENGTH, ASSET_NAME_LENGTH, ASSET_LENGTH, Asset, NOTE_ENCRYPTION_KEY_LENGTH, MAC_LENGTH, ENCRYPTED_NOTE_PLAINTEXT_LENGTH, ENCRYPTED_NOTE_LENGTH, NoteEncrypted, PUBLIC_ADDRESS_LENGTH, RANDOMNESS_LENGTH, MEMO_LENGTH, AMOUNT_VALUE_LENGTH, DECRYPTED_NOTE_LENGTH, Note, PROOF_LENGTH, TRANSACTION_SIGNATURE_LENGTH, TRANSACTION_PUBLIC_KEY_RANDOMNESS_LENGTH, TRANSACTION_EXPIRATION_LENGTH, TRANSACTION_FEE_LENGTH, LATEST_TRANSACTION_VERSION, TransactionPosted, Transaction, verifyTransactions, UnsignedTransaction, LanguageCode, generateKey, spendingKeyToWords, wordsToSpendingKey, generateKeyFromPrivateKey, initializeSapling, FoundBlockResult, ThreadPoolHandler, isValidPublicAddress } = nativeBinding +const { FishHashContext, createSigningCommitment, createSigningShare, ParticipantSecret, ParticipantIdentity, splitSecret, contribute, verifyTransform, KEY_LENGTH, NONCE_LENGTH, BoxKeyPair, randomBytes, boxMessage, unboxMessage, RollingFilter, initSignalHandler, triggerSegfault, ASSET_ID_LENGTH, ASSET_METADATA_LENGTH, ASSET_NAME_LENGTH, ASSET_LENGTH, Asset, NOTE_ENCRYPTION_KEY_LENGTH, MAC_LENGTH, ENCRYPTED_NOTE_PLAINTEXT_LENGTH, ENCRYPTED_NOTE_LENGTH, NoteEncrypted, PUBLIC_ADDRESS_LENGTH, RANDOMNESS_LENGTH, MEMO_LENGTH, AMOUNT_VALUE_LENGTH, DECRYPTED_NOTE_LENGTH, Note, PROOF_LENGTH, TRANSACTION_SIGNATURE_LENGTH, TRANSACTION_PUBLIC_KEY_RANDOMNESS_LENGTH, TRANSACTION_EXPIRATION_LENGTH, TRANSACTION_FEE_LENGTH, LATEST_TRANSACTION_VERSION, TransactionPosted, Transaction, verifyTransactions, UnsignedTransaction, LanguageCode, generateKey, spendingKeyToWords, wordsToSpendingKey, generateKeyFromPrivateKey, initializeSapling, FoundBlockResult, ThreadPoolHandler, isValidPublicAddress } = nativeBinding module.exports.FishHashContext = FishHashContext -module.exports.roundOne = roundOne -module.exports.roundTwo = roundTwo +module.exports.createSigningCommitment = createSigningCommitment +module.exports.createSigningShare = createSigningShare module.exports.ParticipantSecret = ParticipantSecret module.exports.ParticipantIdentity = ParticipantIdentity module.exports.splitSecret = splitSecret diff --git a/ironfish-rust-nodejs/src/frost.rs b/ironfish-rust-nodejs/src/frost.rs index 2a7bccdf74..d4bdeabce6 100644 --- a/ironfish-rust-nodejs/src/frost.rs +++ b/ironfish-rust-nodejs/src/frost.rs @@ -10,7 +10,10 @@ use ironfish::keys::ProofGenerationKeySerializable; use ironfish::{ frost::{keys::KeyPackage, round2::Randomizer, Identifier, SigningPackage}, frost_utils::split_spender_key::split_spender_key, - frost_utils::{round_one::round_one as round_one_rust, round_two::round_two as round_two_rust}, + frost_utils::{ + signing_commitment::create_signing_commitment as create_signing_commitment_rust, + signing_share::create_signing_share as create_signing_share_rust, + }, participant::{Identity, Secret}, serializing::{bytes_to_hex, hex_to_bytes, hex_to_vec_bytes}, SaplingKey, @@ -32,11 +35,11 @@ pub struct NativeIdentifierCommitment { } #[napi] -pub fn round_one(key_package: String, seed: u32) -> Result { +pub fn create_signing_commitment(key_package: String, seed: u32) -> Result { let key_package = KeyPackage::deserialize(&hex_to_vec_bytes(&key_package).map_err(to_napi_err)?) .map_err(to_napi_err)?; - let (_, commitment) = round_one_rust(&key_package, seed as u64); + let (_, commitment) = create_signing_commitment_rust(&key_package, seed as u64); Ok(NativeCommitment { hiding: bytes_to_hex(&commitment.hiding().serialize()), binding: bytes_to_hex(&commitment.binding().serialize()), @@ -44,7 +47,7 @@ pub fn round_one(key_package: String, seed: u32) -> Result { } #[napi] -pub fn round_two( +pub fn create_signing_share( signing_package: String, key_package: String, public_key_randomness: String, @@ -60,8 +63,9 @@ pub fn round_two( Randomizer::deserialize(&hex_to_bytes(&public_key_randomness).map_err(to_napi_err)?) .map_err(to_napi_err)?; - let signature_share = round_two_rust(signing_package, key_package, randomizer, seed as u64) - .map_err(to_napi_err)?; + let signature_share = + create_signing_share_rust(signing_package, key_package, randomizer, seed as u64) + .map_err(to_napi_err)?; Ok(bytes_to_hex(&signature_share.serialize())) } diff --git a/ironfish-rust/src/frost_utils/mod.rs b/ironfish-rust/src/frost_utils/mod.rs index 559dd5588e..8b2ab3c327 100644 --- a/ironfish-rust/src/frost_utils/mod.rs +++ b/ironfish-rust/src/frost_utils/mod.rs @@ -2,7 +2,7 @@ * 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/. */ -pub mod round_one; -pub mod round_two; +pub mod signing_commitment; +pub mod signing_share; pub mod split_secret; pub mod split_spender_key; diff --git a/ironfish-rust/src/frost_utils/round_one.rs b/ironfish-rust/src/frost_utils/signing_commitment.rs similarity index 84% rename from ironfish-rust/src/frost_utils/round_one.rs rename to ironfish-rust/src/frost_utils/signing_commitment.rs index 344260d89d..f690193c4d 100644 --- a/ironfish-rust/src/frost_utils/round_one.rs +++ b/ironfish-rust/src/frost_utils/signing_commitment.rs @@ -10,7 +10,10 @@ use ironfish_frost::frost::{ use rand::{rngs::StdRng, SeedableRng}; // Small wrapper around frost::round1::commit that provides a seedable rng -pub fn round_one(key_package: &KeyPackage, seed: u64) -> (SigningNonces, SigningCommitments) { +pub fn create_signing_commitment( + key_package: &KeyPackage, + seed: u64, +) -> (SigningNonces, SigningCommitments) { let mut rng = StdRng::seed_from_u64(seed); frost::round1::commit(key_package.signing_share(), &mut rng) } @@ -46,8 +49,8 @@ mod test { .next() .expect("key package to be created") .1; - let (nonces, commitments) = super::round_one(&key_package, seed); - let (nonces2, commitments2) = super::round_one(&key_package, seed); + let (nonces, commitments) = super::create_signing_commitment(&key_package, seed); + let (nonces2, commitments2) = super::create_signing_commitment(&key_package, seed); assert_eq!(nonces.hiding().serialize(), nonces2.hiding().serialize()); assert_eq!(nonces.binding().serialize(), nonces2.binding().serialize()); assert_eq!(commitments, commitments2); diff --git a/ironfish-rust/src/frost_utils/round_two.rs b/ironfish-rust/src/frost_utils/signing_share.rs similarity index 97% rename from ironfish-rust/src/frost_utils/round_two.rs rename to ironfish-rust/src/frost_utils/signing_share.rs index 24db90b2d3..a642c13177 100644 --- a/ironfish-rust/src/frost_utils/round_two.rs +++ b/ironfish-rust/src/frost_utils/signing_share.rs @@ -14,7 +14,7 @@ use rand::{rngs::StdRng, SeedableRng}; use crate::errors::{IronfishError, IronfishErrorKind}; // Wrapper around frost::round2::sign that provides a seedable rng from u64 -pub fn round_two( +pub fn create_signing_share( signing_package: SigningPackage, key_package: KeyPackage, randomizer: Randomizer, diff --git a/ironfish-rust/src/transaction/tests.rs b/ironfish-rust/src/transaction/tests.rs index b7815f3b60..bfbe016bd8 100644 --- a/ironfish-rust/src/transaction/tests.rs +++ b/ironfish-rust/src/transaction/tests.rs @@ -7,7 +7,9 @@ use std::collections::BTreeMap; #[cfg(test)] use super::internal_batch_verify_transactions; use super::{ProposedTransaction, Transaction}; -use crate::frost_utils::{round_one::round_one, round_two::round_two}; +use crate::frost_utils::{ + signing_commitment::create_signing_commitment, signing_share::create_signing_share, +}; use crate::transaction::tests::split_spender_key::split_spender_key; use crate::{ assets::{asset::Asset, asset_identifier::NATIVE_ASSET}, @@ -789,7 +791,7 @@ fn test_sign_frost() { // simulate round 1 for key_package in key_packages.key_packages.iter() { - let (_nonce, commitment) = round_one(key_package.1, 0); + let (_nonce, commitment) = create_signing_commitment(key_package.1, 0); commitments.insert(*key_package.0, commitment); } @@ -805,7 +807,7 @@ fn test_sign_frost() { .expect("should be able to deserialize randomizer"); for key_package in key_packages.key_packages.iter() { - let signature_share = round_two( + let signature_share = create_signing_share( signing_package.clone(), key_package.1.clone(), randomizer, diff --git a/ironfish/src/rpc/routes/multisig/createSigningCommitment.ts b/ironfish/src/rpc/routes/multisig/createSigningCommitment.ts index 5c52c8c0f4..52094e95c4 100644 --- a/ironfish/src/rpc/routes/multisig/createSigningCommitment.ts +++ b/ironfish/src/rpc/routes/multisig/createSigningCommitment.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 { roundOne } from '@ironfish/rust-nodejs' +import { createSigningCommitment } from '@ironfish/rust-nodejs' import * as yup from 'yup' import { ApiNamespace } from '../namespaces' import { routes } from '../router' @@ -29,7 +29,7 @@ routes.register { - const result = roundOne(request.data.keyPackage, request.data.seed) + const result = createSigningCommitment(request.data.keyPackage, request.data.seed) request.end({ hiding: result.hiding, diff --git a/ironfish/src/rpc/routes/multisig/createSigningShare.ts b/ironfish/src/rpc/routes/multisig/createSigningShare.ts index bac231c380..e55d33b792 100644 --- a/ironfish/src/rpc/routes/multisig/createSigningShare.ts +++ b/ironfish/src/rpc/routes/multisig/createSigningShare.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 { roundTwo, UnsignedTransaction } from '@ironfish/rust-nodejs' +import { createSigningShare, UnsignedTransaction } from '@ironfish/rust-nodejs' import * as yup from 'yup' import { ApiNamespace } from '../namespaces' import { routes } from '../router' @@ -41,7 +41,7 @@ routes.register { const signingCommitments = [ { identifier: participantA.multiSigKeys.identifier, - commitment: roundOne(participantA.multiSigKeys.keyPackage, seed), + commitment: createSigningCommitment(participantA.multiSigKeys.keyPackage, seed), }, { identifier: participantB.multiSigKeys.identifier, - commitment: roundOne(participantB.multiSigKeys.keyPackage, seed), + commitment: createSigningCommitment(participantB.multiSigKeys.keyPackage, seed), }, { identifier: participantC.multiSigKeys.identifier, - commitment: roundOne(participantC.multiSigKeys.keyPackage, seed), + commitment: createSigningCommitment(participantC.multiSigKeys.keyPackage, seed), }, ] @@ -1281,19 +1281,19 @@ describe('Wallet', () => { const publicKeyRandomness = unsignedTransaction.publicKeyRandomness() const signatureShares: Record = { - [participantA.multiSigKeys.identifier]: roundTwo( + [participantA.multiSigKeys.identifier]: createSigningShare( signingPackage, participantA.multiSigKeys.keyPackage, publicKeyRandomness, seed, ), - [participantB.multiSigKeys.identifier]: roundTwo( + [participantB.multiSigKeys.identifier]: createSigningShare( signingPackage, participantB.multiSigKeys.keyPackage, publicKeyRandomness, seed, ), - [participantC.multiSigKeys.identifier]: roundTwo( + [participantC.multiSigKeys.identifier]: createSigningShare( signingPackage, participantC.multiSigKeys.keyPackage, publicKeyRandomness, From 3e38f8b1b5ddc58591add3b08f9bbef527b3389b Mon Sep 17 00:00:00 2001 From: Rahul Patni Date: Tue, 30 Jan 2024 19:17:39 -0800 Subject: [PATCH 21/27] replacing pgk with nsk (#4647) * replacing pgk with nsk * updating useunsignedtxfixture to use nsk: * update unsigned.test.slow.ts --- ironfish-rust-nodejs/index.d.ts | 2 +- ironfish-rust-nodejs/src/lib.rs | 12 ++++-- .../tests/unsigned.test.slow.ts | 40 +++++++++---------- .../testUtilities/fixtures/transactions.ts | 6 ++- 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index f413e7f7f4..5c2a1d9c57 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -90,7 +90,7 @@ export interface Key { incomingViewKey: string outgoingViewKey: string publicAddress: string - proofGenerationKey: string + proofAuthorizingKey: string } export function generateKey(): Key export function spendingKeyToWords(privateKey: string, languageCode: LanguageCode): string diff --git a/ironfish-rust-nodejs/src/lib.rs b/ironfish-rust-nodejs/src/lib.rs index 7d910a2248..c6a95c060c 100644 --- a/ironfish-rust-nodejs/src/lib.rs +++ b/ironfish-rust-nodejs/src/lib.rs @@ -5,7 +5,7 @@ use std::fmt::Display; use ironfish::keys::Language; -use ironfish::keys::ProofGenerationKeySerializable; +use ironfish::serializing::bytes_to_hex; use ironfish::PublicAddress; use ironfish::SaplingKey; @@ -65,7 +65,7 @@ pub struct Key { pub incoming_view_key: String, pub outgoing_view_key: String, pub public_address: String, - pub proof_generation_key: String, + pub proof_authorizing_key: String, } #[napi] @@ -78,7 +78,9 @@ pub fn generate_key() -> Key { incoming_view_key: sapling_key.incoming_view_key().hex_key(), outgoing_view_key: sapling_key.outgoing_view_key().hex_key(), public_address: sapling_key.public_address().hex_public_address(), - proof_generation_key: sapling_key.sapling_proof_generation_key().hex_key(), + proof_authorizing_key: bytes_to_hex( + &sapling_key.sapling_proof_generation_key().nsk.to_bytes(), + ), } } @@ -105,7 +107,9 @@ pub fn generate_key_from_private_key(private_key: String) -> Result { incoming_view_key: sapling_key.incoming_view_key().hex_key(), outgoing_view_key: sapling_key.outgoing_view_key().hex_key(), public_address: sapling_key.public_address().hex_public_address(), - proof_generation_key: sapling_key.sapling_proof_generation_key().hex_key(), + proof_authorizing_key: bytes_to_hex( + &sapling_key.sapling_proof_generation_key().nsk.to_bytes(), + ), }) } diff --git a/ironfish-rust-nodejs/tests/unsigned.test.slow.ts b/ironfish-rust-nodejs/tests/unsigned.test.slow.ts index 023b7e4f41..7aee70d933 100644 --- a/ironfish-rust-nodejs/tests/unsigned.test.slow.ts +++ b/ironfish-rust-nodejs/tests/unsigned.test.slow.ts @@ -2,26 +2,24 @@ * 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 { UnsignedTransaction } from ".." -import { Asset, Transaction, generateKey } from ".." +import { Asset, Transaction, UnsignedTransaction, generateKey } from ".."; -describe('UnsignedTransaction', () => { - describe('ser/de', () => { - it('can create an unsigned tx and deserialize it', () => { - const key = generateKey() - const asset = new Asset(key.publicAddress, 'testcoin', '') - const proposedTx = new Transaction(2) - proposedTx.mint(asset, 5n) - const unsignedTxBuffer = proposedTx.build( - key.proofGenerationKey, - key.viewKey, - key.outgoingViewKey, - 0n, - ) +describe("UnsignedTransaction", () => { + describe("ser/de", () => { + it("can create an unsigned tx and deserialize it", () => { + const key = generateKey(); + const asset = new Asset(key.publicAddress, "testcoin", ""); + const proposedTx = new Transaction(2); + proposedTx.mint(asset, 5n); + const unsignedTxBuffer = proposedTx.build( + key.viewKey.slice(0, 64) + key.proofAuthorizingKey, //todo(rahul): change this to accept just proof authorizing key when the interface changes + key.viewKey, + key.outgoingViewKey, + 0n + ); - const unsignedTx = new UnsignedTransaction(unsignedTxBuffer) - expect(unsignedTx.serialize()).toEqual(unsignedTxBuffer) - - }) - }) -}) + const unsignedTx = new UnsignedTransaction(unsignedTxBuffer); + expect(unsignedTx.serialize()).toEqual(unsignedTxBuffer); + }); + }); +}); diff --git a/ironfish/src/testUtilities/fixtures/transactions.ts b/ironfish/src/testUtilities/fixtures/transactions.ts index 30bc1099a8..36003b283f 100644 --- a/ironfish/src/testUtilities/fixtures/transactions.ts +++ b/ironfish/src/testUtilities/fixtures/transactions.ts @@ -128,7 +128,11 @@ export async function useUnsignedTxFixture( Assert.isNotNull(from.spendingKey) const key = generateKeyFromPrivateKey(from.spendingKey) const unsignedBuffer = raw - .build(key.proofGenerationKey, key.viewKey, key.outgoingViewKey) + .build( + key.viewKey.slice(0, 64) + key.proofAuthorizingKey, //todo(rahul): change this to accept just proof authorizing key when the interface changes + key.viewKey, + key.outgoingViewKey, + ) .serialize() return new UnsignedTransaction(unsignedBuffer) }) From 92e19f634be62f6c13221360bc43aebedf96fc11 Mon Sep 17 00:00:00 2001 From: Rahul Patni Date: Wed, 31 Jan 2024 13:16:03 -0800 Subject: [PATCH 22/27] adding serializable trait to jubjub::Fr (#4649) * adding serializable trait to jubjub::Fr * adds InvalidProofAuthorizingKey * variable name clean up * fixing test --- ironfish-rust/src/errors.rs | 1 + ironfish-rust/src/serializing/fr.rs | 99 ++++++++++++++++++++++++++++ ironfish-rust/src/serializing/mod.rs | 2 +- 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 ironfish-rust/src/serializing/fr.rs diff --git a/ironfish-rust/src/errors.rs b/ironfish-rust/src/errors.rs index 872d754f65..446e1ea077 100644 --- a/ironfish-rust/src/errors.rs +++ b/ironfish-rust/src/errors.rs @@ -39,6 +39,7 @@ pub enum IronfishErrorKind { InvalidDecryptionKey, InvalidDiversificationPoint, InvalidEntropy, + InvalidFr, InvalidLanguageEncoding, InvalidMinersFeeTransaction, InvalidMintProof, diff --git a/ironfish-rust/src/serializing/fr.rs b/ironfish-rust/src/serializing/fr.rs new file mode 100644 index 0000000000..19cc103b38 --- /dev/null +++ b/ironfish-rust/src/serializing/fr.rs @@ -0,0 +1,99 @@ +/* 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/. */ + +use crate::errors::{IronfishError, IronfishErrorKind}; +use jubjub::Fr; + +use super::{bytes_to_hex, hex_to_bytes}; + +pub trait FrSerializable { + fn serialize(&self) -> [u8; 32]; + fn deserialize(bytes: [u8; 32]) -> Result; + fn hex_key(&self) -> String; + fn from_hex(hex_key: &str) -> Result; +} + +impl FrSerializable for Fr { + fn serialize(&self) -> [u8; 32] { + self.to_bytes() + } + + fn deserialize(bytes: [u8; 32]) -> Result { + let fr = match Fr::from_bytes(&bytes).into() { + Some(fr) => fr, + None => return Err(IronfishError::new(IronfishErrorKind::InvalidFr)), + }; + + Ok(fr) + } + + fn hex_key(&self) -> String { + bytes_to_hex(&self.serialize()) + } + + fn from_hex(hex_key: &str) -> Result { + let bytes = hex_to_bytes(hex_key)?; + Fr::deserialize(bytes) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::errors::IronfishErrorKind; + use ff::Field; + use rand::{rngs::StdRng, SeedableRng}; + + #[test] + fn test_serialize() { + let mut rng = StdRng::seed_from_u64(0); + + let fr = Fr::random(&mut rng); + + let serialized_bytes = fr.serialize(); + + assert_eq!(serialized_bytes.len(), 32); + } + + #[test] + fn test_deserialize_error() { + let mut bytes: [u8; 32] = [0; 32]; + bytes[0..32].fill(0xFF); + + let result = Fr::deserialize(bytes); + + assert!(result.is_err()); + + let err = result.err().unwrap(); + + assert!(matches!(err.kind, IronfishErrorKind::InvalidFr)); + } + + #[test] + fn test_deserialize() { + let mut rng = StdRng::seed_from_u64(0); + + let fr = Fr::random(&mut rng); + + let serialized_bytes = fr.serialize(); + + let deserialized_fr = + Fr::deserialize(serialized_bytes).expect("deserialization successful"); + + assert_eq!(fr, deserialized_fr); + } + + #[test] + fn test_hex() { + let mut rng = StdRng::seed_from_u64(0); + + let fr = jubjub::Fr::random(&mut rng); + + let hex_key = fr.hex_key(); + + let deserialized_fr = Fr::from_hex(&hex_key).expect("deserialization successful"); + + assert_eq!(fr, deserialized_fr); + } +} diff --git a/ironfish-rust/src/serializing/mod.rs b/ironfish-rust/src/serializing/mod.rs index 0d2ee4d68c..8e8752edce 100644 --- a/ironfish-rust/src/serializing/mod.rs +++ b/ironfish-rust/src/serializing/mod.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ pub mod aead; - +pub mod fr; use crate::errors::{IronfishError, IronfishErrorKind}; /// Helper functions to convert pairing parts to bytes From 9085db3a896ea2976660ad8ef561a681564faa1e Mon Sep 17 00:00:00 2001 From: Rahul Patni Date: Wed, 31 Jan 2024 13:49:43 -0800 Subject: [PATCH 23/27] Rahul/ifl 2146 replace pgk on build with proof authorizing key (#4650) * adding invalid authorizing key error * replacing pgk with proof authorizing key * updating rust node js layer with proof authorizing key * variable name clean up * removing test todo * build method for raw transaction --- Cargo.lock | 1 + ironfish-rust-nodejs/Cargo.toml | 1 + ironfish-rust-nodejs/index.d.ts | 2 +- ironfish-rust-nodejs/src/structs/transaction.rs | 11 ++++++----- ironfish-rust-nodejs/tests/unsigned.test.slow.ts | 2 +- ironfish-rust/src/transaction/mod.rs | 11 ++++++++--- ironfish-rust/src/transaction/tests.rs | 6 +++--- ironfish/src/primitives/rawTransaction.ts | 4 ++-- ironfish/src/testUtilities/fixtures/transactions.ts | 6 +----- ironfish/src/wallet/wallet.test.slow.ts | 2 +- 10 files changed, 25 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d7d857266..ccf3d714e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1578,6 +1578,7 @@ dependencies = [ "fish_hash", "ironfish", "ironfish_mpc", + "jubjub 0.9.0 (git+https://github.com/iron-fish/jubjub.git?branch=blstrs)", "napi", "napi-build", "napi-derive", diff --git a/ironfish-rust-nodejs/Cargo.toml b/ironfish-rust-nodejs/Cargo.toml index 8208f5dd2d..11417cbcb9 100644 --- a/ironfish-rust-nodejs/Cargo.toml +++ b/ironfish-rust-nodejs/Cargo.toml @@ -32,6 +32,7 @@ ironfish = { path = "../ironfish-rust" } ironfish_mpc = { path = "../ironfish-mpc" } napi = { version = "2.13.2", features = ["napi6"] } napi-derive = "2.13.0" +jubjub = { git = "https://github.com/iron-fish/jubjub.git", branch = "blstrs" } rand = "0.8.5" [build-dependencies] diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index 5c2a1d9c57..d3a126bbc1 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -242,7 +242,7 @@ export class Transaction { * aka: self.value_balance - intended_transaction_fee - change = 0 */ post(spenderHexKey: string, changeGoesTo: string | undefined | null, intendedTransactionFee: bigint): Buffer - build(proofGenerationKeyStr: string, viewKeyStr: string, outgoingViewKeyStr: string, intendedTransactionFee: bigint, changeGoesTo?: string | undefined | null): Buffer + build(proofAuthorizingKeyStr: string, viewKeyStr: string, outgoingViewKeyStr: string, intendedTransactionFee: bigint, changeGoesTo?: string | undefined | null): Buffer setExpiration(sequence: number): void } export type NativeUnsignedTransaction = UnsignedTransaction diff --git a/ironfish-rust-nodejs/src/structs/transaction.rs b/ironfish-rust-nodejs/src/structs/transaction.rs index 3d7459df09..dadfea99ee 100644 --- a/ironfish-rust-nodejs/src/structs/transaction.rs +++ b/ironfish-rust-nodejs/src/structs/transaction.rs @@ -14,6 +14,7 @@ use ironfish::frost::round1::SigningCommitments; use ironfish::frost::round2::SignatureShare; use ironfish::frost::Identifier; use ironfish::frost::SigningPackage; +use ironfish::serializing::fr::FrSerializable; use ironfish::serializing::hex_to_vec_bytes; use ironfish::serializing::{bytes_to_hex, hex_to_bytes}; use ironfish::transaction::unsigned::UnsignedTransaction; @@ -22,7 +23,6 @@ use ironfish::transaction::{ TRANSACTION_FEE_SIZE, TRANSACTION_PUBLIC_KEY_SIZE, TRANSACTION_SIGNATURE_SIZE, }; use ironfish::{ - keys::proof_generation_key::{ProofGenerationKey, ProofGenerationKeySerializable}, MerkleNoteHash, OutgoingViewKey, ProposedTransaction, PublicAddress, SaplingKey, Transaction, ViewKey, }; @@ -327,7 +327,7 @@ impl NativeTransaction { #[napi] pub fn build( &mut self, - proof_generation_key_str: String, + proof_authorizing_key_str: String, view_key_str: String, outgoing_view_key_str: String, intended_transaction_fee: BigInt, @@ -336,8 +336,9 @@ impl NativeTransaction { let view_key = ViewKey::from_hex(&view_key_str).map_err(to_napi_err)?; let outgoing_view_key = OutgoingViewKey::from_hex(&outgoing_view_key_str).map_err(to_napi_err)?; - let proof_generation_key = ProofGenerationKey::from_hex(&proof_generation_key_str) - .map_err(|_| to_napi_err("PublicKeyPackage hex to bytes failed"))?; + let proof_authorizing_key = jubjub::Fr::from_hex(&proof_authorizing_key_str) + .map_err(|_| to_napi_err("PublicKeyPackage authorizing key hex to bytes failed"))?; + let change_address = match change_goes_to { Some(address) => Some(PublicAddress::from_hex(&address).map_err(to_napi_err)?), None => None, @@ -345,7 +346,7 @@ impl NativeTransaction { let unsigned_transaction = self .transaction .build( - proof_generation_key, + proof_authorizing_key, view_key, outgoing_view_key, intended_transaction_fee.get_i64().0, diff --git a/ironfish-rust-nodejs/tests/unsigned.test.slow.ts b/ironfish-rust-nodejs/tests/unsigned.test.slow.ts index 7aee70d933..d7697d699a 100644 --- a/ironfish-rust-nodejs/tests/unsigned.test.slow.ts +++ b/ironfish-rust-nodejs/tests/unsigned.test.slow.ts @@ -12,7 +12,7 @@ describe("UnsignedTransaction", () => { const proposedTx = new Transaction(2); proposedTx.mint(asset, 5n); const unsignedTxBuffer = proposedTx.build( - key.viewKey.slice(0, 64) + key.proofAuthorizingKey, //todo(rahul): change this to accept just proof authorizing key when the interface changes + key.proofAuthorizingKey, key.viewKey, key.outgoingViewKey, 0n diff --git a/ironfish-rust/src/transaction/mod.rs b/ironfish-rust/src/transaction/mod.rs index 7d3f3febd2..dbbab6a8e8 100644 --- a/ironfish-rust/src/transaction/mod.rs +++ b/ironfish-rust/src/transaction/mod.rs @@ -231,7 +231,7 @@ impl ProposedTransaction { pub fn build( &mut self, - proof_generation_key: ProofGenerationKey, + proof_authorizing_key: jubjub::Fr, view_key: ViewKey, outgoing_view_key: OutgoingViewKey, intended_transaction_fee: i64, @@ -239,6 +239,11 @@ impl ProposedTransaction { ) -> Result { let public_address = view_key.public_address()?; + let proof_generation_key = ProofGenerationKey { + ak: view_key.authorizing_key, + nsk: proof_authorizing_key, + }; + // skip adding change notes if this is special case of a miners fee transaction let is_miners_fee = self.outputs.iter().any(|output| output.get_is_miners_fee()); if !is_miners_fee { @@ -337,7 +342,7 @@ impl ProposedTransaction { let i64_fee = i64::try_from(intended_transaction_fee)?; let unsigned = self.build( - spender_key.sapling_proof_generation_key(), + spender_key.proof_authorizing_key, spender_key.view_key().clone(), spender_key.outgoing_view_key().clone(), i64_fee, @@ -377,7 +382,7 @@ impl ProposedTransaction { output.set_is_miners_fee(); } let unsigned = self.build( - spender_key.sapling_proof_generation_key(), + spender_key.proof_authorizing_key, spender_key.view_key().clone(), spender_key.outgoing_view_key().clone(), *self.value_balances.fee(), diff --git a/ironfish-rust/src/transaction/tests.rs b/ironfish-rust/src/transaction/tests.rs index bfbe016bd8..64dd7b99e1 100644 --- a/ironfish-rust/src/transaction/tests.rs +++ b/ironfish-rust/src/transaction/tests.rs @@ -242,7 +242,7 @@ fn test_proposed_transaction_build() { let unsigned_transaction = transaction .build( - spender_key.sapling_proof_generation_key(), + spender_key.proof_authorizing_key, spender_key.view_key().clone(), spender_key.outgoing_view_key().clone(), intended_fee, @@ -685,7 +685,7 @@ fn test_sign_simple() { // build transaction, generate proofs let unsigned_transaction = transaction .build( - spender_key.sapling_proof_generation_key(), + spender_key.proof_authorizing_key, spender_key.view_key().clone(), spender_key.outgoing_view_key().clone(), 1, @@ -779,7 +779,7 @@ fn test_sign_frost() { // build UnsignedTransaction without signing let mut unsigned_transaction = transaction .build( - key_packages.proof_generation_key, + key_packages.proof_generation_key.nsk, key_packages.view_key, key_packages.outgoing_view_key, intended_fee, diff --git a/ironfish/src/primitives/rawTransaction.ts b/ironfish/src/primitives/rawTransaction.ts index 42846856ba..4343c3bd9c 100644 --- a/ironfish/src/primitives/rawTransaction.ts +++ b/ironfish/src/primitives/rawTransaction.ts @@ -163,14 +163,14 @@ export class RawTransaction { } build( - proofGenerationKey: string, + proofAuthorizingKey: string, viewKey: string, outgoingViewKey: string, ): UnsignedTransaction { const builder = this._build() const serialized = builder.build( - proofGenerationKey, + proofAuthorizingKey, viewKey, outgoingViewKey, this.fee, diff --git a/ironfish/src/testUtilities/fixtures/transactions.ts b/ironfish/src/testUtilities/fixtures/transactions.ts index 36003b283f..1f81ad9898 100644 --- a/ironfish/src/testUtilities/fixtures/transactions.ts +++ b/ironfish/src/testUtilities/fixtures/transactions.ts @@ -128,11 +128,7 @@ export async function useUnsignedTxFixture( Assert.isNotNull(from.spendingKey) const key = generateKeyFromPrivateKey(from.spendingKey) const unsignedBuffer = raw - .build( - key.viewKey.slice(0, 64) + key.proofAuthorizingKey, //todo(rahul): change this to accept just proof authorizing key when the interface changes - key.viewKey, - key.outgoingViewKey, - ) + .build(key.proofAuthorizingKey, key.viewKey, key.outgoingViewKey) .serialize() return new UnsignedTransaction(unsignedBuffer) }) diff --git a/ironfish/src/wallet/wallet.test.slow.ts b/ironfish/src/wallet/wallet.test.slow.ts index c6fb98a20f..9b7a1c1185 100644 --- a/ironfish/src/wallet/wallet.test.slow.ts +++ b/ironfish/src/wallet/wallet.test.slow.ts @@ -1272,7 +1272,7 @@ describe('Wallet', () => { }) const unsignedTransaction = rawTransaction.build( - trustedDealerPackage.proofGenerationKey, + trustedDealerPackage.proofGenerationKey.slice(64, 128), trustedDealerPackage.viewKey, trustedDealerPackage.outgoingViewKey, ) From 35bfdbe64d7e24601883d946a34bd1b6f448bd71 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Wed, 31 Jan 2024 14:55:56 -0800 Subject: [PATCH 24/27] updates TrustedDealerKeyPackage to use proofAuthorizingKey (#4651) * updates TrustedDealerKeyPackage to use proofAuthorizingKey we plan to store proofAuthorizingKey (nsk) on each account in the walletDb instead of storing proofGenerationKey (ak, nsk) updates split_secret to return proofAuthorizingKey as part of the TrustedDealerKeyPackage instead of proofGenerationKey propagates changes through napi * removes unused imports * uses FrSerializable and hex_key instead of bytes_to_hex --- ironfish-rust-nodejs/index.d.ts | 2 +- ironfish-rust-nodejs/src/frost.rs | 5 ++--- ironfish-rust-nodejs/src/structs/key_packages.rs | 2 +- ironfish-rust/src/frost_utils/split_spender_key.rs | 11 ++++------- ironfish-rust/src/transaction/tests.rs | 2 +- .../routes/multisig/createTrustedDealerKeyPackage.ts | 8 +++++++- ironfish/src/wallet/wallet.test.slow.ts | 9 +++++++-- 7 files changed, 23 insertions(+), 16 deletions(-) diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index d3a126bbc1..75b9cf2793 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -66,7 +66,7 @@ export interface IdentiferKeyPackage { } export interface TrustedDealerKeyPackages { verifyingKey: string - proofGenerationKey: string + proofAuthorizingKey: string viewKey: string incomingViewKey: string outgoingViewKey: string diff --git a/ironfish-rust-nodejs/src/frost.rs b/ironfish-rust-nodejs/src/frost.rs index d4bdeabce6..a58a4a497f 100644 --- a/ironfish-rust-nodejs/src/frost.rs +++ b/ironfish-rust-nodejs/src/frost.rs @@ -6,7 +6,6 @@ use crate::{ structs::{IdentiferKeyPackage, TrustedDealerKeyPackages}, to_napi_err, }; -use ironfish::keys::ProofGenerationKeySerializable; use ironfish::{ frost::{keys::KeyPackage, round2::Randomizer, Identifier, SigningPackage}, frost_utils::split_spender_key::split_spender_key, @@ -15,7 +14,7 @@ use ironfish::{ signing_share::create_signing_share as create_signing_share_rust, }, participant::{Identity, Secret}, - serializing::{bytes_to_hex, hex_to_bytes, hex_to_vec_bytes}, + serializing::{bytes_to_hex, fr::FrSerializable, hex_to_bytes, hex_to_vec_bytes}, SaplingKey, }; use napi::{bindgen_prelude::*, JsBuffer}; @@ -177,7 +176,7 @@ pub fn split_secret( Ok(TrustedDealerKeyPackages { verifying_key: bytes_to_hex(&t.verifying_key), - proof_generation_key: t.proof_generation_key.hex_key(), + proof_authorizing_key: t.proof_authorizing_key.hex_key(), view_key: t.view_key.hex_key(), incoming_view_key: t.incoming_view_key.hex_key(), outgoing_view_key: t.outgoing_view_key.hex_key(), diff --git a/ironfish-rust-nodejs/src/structs/key_packages.rs b/ironfish-rust-nodejs/src/structs/key_packages.rs index 2410cabb61..7237149d70 100644 --- a/ironfish-rust-nodejs/src/structs/key_packages.rs +++ b/ironfish-rust-nodejs/src/structs/key_packages.rs @@ -13,7 +13,7 @@ pub struct IdentiferKeyPackage { pub struct TrustedDealerKeyPackages { pub verifying_key: String, - pub proof_generation_key: String, + pub proof_authorizing_key: String, pub view_key: String, pub incoming_view_key: String, pub outgoing_view_key: String, diff --git a/ironfish-rust/src/frost_utils/split_spender_key.rs b/ironfish-rust/src/frost_utils/split_spender_key.rs index 2b93b4fb64..e7c8bce14e 100644 --- a/ironfish-rust/src/frost_utils/split_spender_key.rs +++ b/ironfish-rust/src/frost_utils/split_spender_key.rs @@ -7,7 +7,7 @@ use ironfish_frost::frost::{ keys::{IdentifierList, KeyPackage, PublicKeyPackage}, Identifier, }; -use ironfish_zkp::{constants::PROOF_GENERATION_KEY_GENERATOR, ProofGenerationKey}; +use ironfish_zkp::constants::PROOF_GENERATION_KEY_GENERATOR; use jubjub::SubgroupPoint; use rand::thread_rng; use std::collections::HashMap; @@ -23,7 +23,7 @@ type AuthorizingKey = [u8; 32]; pub struct TrustedDealerKeyPackages { pub verifying_key: AuthorizingKey, // verifying_key is the name given to this field in the frost protocol - pub proof_generation_key: ProofGenerationKey, + pub proof_authorizing_key: jubjub::Fr, pub view_key: ViewKey, pub incoming_view_key: IncomingViewKey, pub outgoing_view_key: OutgoingViewKey, @@ -61,10 +61,7 @@ pub fn split_spender_key( let authorizing_key = Option::from(SubgroupPoint::from_bytes(&authorizing_key_bytes)) .ok_or_else(|| IronfishError::new(IronfishErrorKind::InvalidAuthorizingKey))?; - let proof_generation_key = ProofGenerationKey { - ak: authorizing_key, - nsk: coordinator_sapling_key.sapling_proof_generation_key().nsk, - }; + let proof_authorizing_key = coordinator_sapling_key.sapling_proof_generation_key().nsk; let nullifier_deriving_key = *PROOF_GENERATION_KEY_GENERATOR * coordinator_sapling_key.sapling_proof_generation_key().nsk; @@ -82,7 +79,7 @@ pub fn split_spender_key( Ok(TrustedDealerKeyPackages { verifying_key: authorizing_key_bytes, - proof_generation_key, + proof_authorizing_key, view_key, incoming_view_key, outgoing_view_key, diff --git a/ironfish-rust/src/transaction/tests.rs b/ironfish-rust/src/transaction/tests.rs index 64dd7b99e1..2a96fc74a3 100644 --- a/ironfish-rust/src/transaction/tests.rs +++ b/ironfish-rust/src/transaction/tests.rs @@ -779,7 +779,7 @@ fn test_sign_frost() { // build UnsignedTransaction without signing let mut unsigned_transaction = transaction .build( - key_packages.proof_generation_key.nsk, + key_packages.proof_authorizing_key, key_packages.view_key, key_packages.outgoing_view_key, intended_fee, diff --git a/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.ts b/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.ts index aa250e37de..5075e38a67 100644 --- a/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.ts +++ b/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.ts @@ -80,6 +80,12 @@ routes.register< maxSigners, identifiers, ) - request.end(trustedDealerPackage) + + // TODO(hughy): update response type to use proofAuthorizingKey + const proofGenerationKey = trustedDealerPackage.viewKey + .slice(0, 64) + .concat(trustedDealerPackage.proofAuthorizingKey) + + request.end({ ...trustedDealerPackage, proofGenerationKey }) }, ) diff --git a/ironfish/src/wallet/wallet.test.slow.ts b/ironfish/src/wallet/wallet.test.slow.ts index 9b7a1c1185..d263fdc628 100644 --- a/ironfish/src/wallet/wallet.test.slow.ts +++ b/ironfish/src/wallet/wallet.test.slow.ts @@ -1162,11 +1162,16 @@ describe('Wallet', () => { identifiers, ) + // TODO(hughy): replace when account imports use proofAuthorizingKey + const proofGenerationKey = trustedDealerPackage.viewKey + .slice(0, 64) + .concat(trustedDealerPackage.proofAuthorizingKey) + const getMultiSigKeys = (index: number) => { return { identifier: trustedDealerPackage.keyPackages[index].identifier, keyPackage: trustedDealerPackage.keyPackages[index].keyPackage, - proofGenerationKey: trustedDealerPackage.proofGenerationKey, + proofGenerationKey: proofGenerationKey, } } @@ -1272,7 +1277,7 @@ describe('Wallet', () => { }) const unsignedTransaction = rawTransaction.build( - trustedDealerPackage.proofGenerationKey.slice(64, 128), + trustedDealerPackage.proofAuthorizingKey, trustedDealerPackage.viewKey, trustedDealerPackage.outgoingViewKey, ) From bb70ff28b239454852670ed124770c05a371cc1d Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:00:03 -0800 Subject: [PATCH 25/27] updates createTrustedDealerKeyPackage to return nsk (#4653) RPC returns proofAuthorizingKey (nsk) instead of proofGenerationKey --- .../multisig/createTrustedDealerKeyPackage.test.ts | 2 +- .../routes/multisig/createTrustedDealerKeyPackage.ts | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.test.ts b/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.test.ts index 1b16af75c1..b0805770ec 100644 --- a/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.test.ts +++ b/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.test.ts @@ -33,7 +33,7 @@ describe('Route multisig/createTrustedDealerKeyPackage', () => { }, ]), outgoingViewKey: expect.any(String), - proofGenerationKey: expect.any(String), + proofAuthorizingKey: expect.any(String), publicAddress: expect.any(String), publicKeyPackage: expect.any(String), verifyingKey: expect.any(String), diff --git a/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.ts b/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.ts index 5075e38a67..090473af24 100644 --- a/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.ts +++ b/ironfish/src/rpc/routes/multisig/createTrustedDealerKeyPackage.ts @@ -15,7 +15,7 @@ export type CreateTrustedDealerKeyPackageRequest = { } export type CreateTrustedDealerKeyPackageResponse = { verifyingKey: string - proofGenerationKey: string + proofAuthorizingKey: string viewKey: string incomingViewKey: string outgoingViewKey: string @@ -45,7 +45,7 @@ export const CreateTrustedDealerKeyPackageResponseSchema: yup.ObjectSchema Date: Wed, 31 Jan 2024 17:44:02 -0800 Subject: [PATCH 26/27] fixes Bech32Encoder backwards compatibility (#4654) we recently released a change that added an optional field to the end of bech32 account encodings. however, we did not update the encoder to correctly handle older encodings that are missing that optional field if a user attempts to import an older bech32 account they will see an error when the Bech32Encoder attempts to read a byte after the end of their import string updates the Bech32Encoder to support decoding multiple versions: - defines 'Bech32Decoder' as a function that takes a reader and decoding options and returns an AccountImport - adds a mapping to Bech32Encoder that maps supported versions to decode functions for those versions adds a test case for a bech32 account from v1.16.0 --- .../__importTestCases__/1p16p0_bech32.txt | 1 + .../src/wallet/account/encoder/bech32.test.ts | 4 +- ironfish/src/wallet/account/encoder/bech32.ts | 124 +++++++++++------- 3 files changed, 75 insertions(+), 54 deletions(-) create mode 100644 ironfish/src/rpc/routes/wallet/__importTestCases__/1p16p0_bech32.txt diff --git a/ironfish/src/rpc/routes/wallet/__importTestCases__/1p16p0_bech32.txt b/ironfish/src/rpc/routes/wallet/__importTestCases__/1p16p0_bech32.txt new file mode 100644 index 0000000000..ba29ff6cc0 --- /dev/null +++ b/ironfish/src/rpc/routes/wallet/__importTestCases__/1p16p0_bech32.txt @@ -0,0 +1 @@ +ifaccount1xqcnqvpsvsenzdesxvcnxd3hxqenqdtxxcervdfkxvmrsvenxvexywr9venxgcnrx9jkydtyxvmnjctyxpjxxdt9x5ex2cn9vymnydf3x5cxzcmr89jnqe3jvvcrzce38yck2vnyv93xvwt9xuexvc3svvcrgvfkxgmrvef5xf3rgep5vejnvefe89nxzdnyxccr2enz8qcnvdnzxqukyepjxvcr2vf4vvenqvnxxyungvpexajnzv3jvfnrvwpsvvuxvc3nx3nrxvec8pjrydnzxcunjetp8yekvwps8q6nyefjv3nrye33x43nscmyv56rxdtzx33nqdfkvdnrwepcxejrqdf4xyerqd34893nzc3cvscnvetxvs6kxcmyv4nxvdph8q6nxve5xs6rsv3sx5mxyep3vf3xxetpx4nr2d3jvymrycekx33kxc3hxucnjve5v4jnwdfjxdjxgc3hxvukyvpcx5uxzeryv5crsdmpx5ck2d3nxa3k2dpkxgex2enyv3snycfnxa3kyvfsxf3xgd34vgmnsve3vcekzefevdnrqvtrvgmngwfsxs6kzvfnxqmrwvekvdjnvwryxanrxc33xa3nqerpxyenqdpjvfsnvdrxxvmkzdr9vgurgd3jxg6ngvnpvcexze3hxymn2vp3xqcrqvpsxqcrqvpsxs6nyvmzvsmrqdtpxsmxxctrvsekxepkxvckvvrx8yunqwpk8pjxvvfkxanrwcejx33nwvt9ve3nvd34x5mxvdtpvf3njvp4xqcqjm5j8h \ No newline at end of file diff --git a/ironfish/src/wallet/account/encoder/bech32.test.ts b/ironfish/src/wallet/account/encoder/bech32.test.ts index 1f635cef36..f811d4bba3 100644 --- a/ironfish/src/wallet/account/encoder/bech32.test.ts +++ b/ironfish/src/wallet/account/encoder/bech32.test.ts @@ -110,7 +110,7 @@ describe('Bech32AccountEncoder', () => { expect(() => encoder.decode(encoded)).toThrow() }) - it('throws an error when decoding if the version does not match', () => { + it('throws an error when decoding if the version is not supported', () => { const accountImport: AccountImport = { version: ACCOUNT_SCHEMA_VERSION, name: 'test', @@ -127,8 +127,6 @@ describe('Bech32AccountEncoder', () => { const encoded = encoder.encode(accountImport) expect(encoded.startsWith(BECH32_ACCOUNT_PREFIX)).toBe(true) - encoder.VERSION = 1 - expect(() => encoder.decode(encoded)).toThrow() }) }) diff --git a/ironfish/src/wallet/account/encoder/bech32.ts b/ironfish/src/wallet/account/encoder/bech32.ts index e5efa7293f..59b6c60759 100644 --- a/ironfish/src/wallet/account/encoder/bech32.ts +++ b/ironfish/src/wallet/account/encoder/bech32.ts @@ -9,8 +9,19 @@ import { ACCOUNT_SCHEMA_VERSION } from '../account' import { AccountDecodingOptions, AccountEncoder, DecodeFailed, DecodeInvalid } from './encoder' export const BECH32_ACCOUNT_PREFIX = 'ifaccount' + +type Bech32Decoder = ( + reader: bufio.BufferReader, + options?: AccountDecodingOptions, +) => AccountImport + export class Bech32Encoder implements AccountEncoder { - VERSION = 1 + VERSION = 2 + + VERSION_DECODERS: Map = new Map([ + [1, decoderV1], + [2, decoderV2], + ]) encode(value: AccountImport): string { const bw = bufio.write(this.getSize(value)) @@ -53,15 +64,6 @@ export class Bech32Encoder implements AccountEncoder { ) } - let name: string - let viewKey: string - let incomingViewKey: string - let outgoingViewKey: string - let publicAddress: string - let spendingKey: string | null - let createdAt = null - let multiSigKeys = undefined - try { const buffer = Buffer.from(hexEncoding, 'hex') @@ -69,38 +71,13 @@ export class Bech32Encoder implements AccountEncoder { const version = reader.readU16() - if (version !== this.VERSION) { - throw new DecodeInvalid( - `Encoded account version ${version} does not match encoder version ${this.VERSION}`, - ) - } - - name = reader.readVarString('utf8') - viewKey = reader.readBytes(VIEW_KEY_LENGTH).toString('hex') - incomingViewKey = reader.readBytes(KEY_LENGTH).toString('hex') - outgoingViewKey = reader.readBytes(KEY_LENGTH).toString('hex') - publicAddress = reader.readBytes(PUBLIC_ADDRESS_LENGTH).toString('hex') + const decoder = this.VERSION_DECODERS.get(version) - const hasSpendingKey = reader.readU8() === 1 - spendingKey = hasSpendingKey ? reader.readBytes(KEY_LENGTH).toString('hex') : null - - const hasCreatedAt = reader.readU8() === 1 - - if (hasCreatedAt) { - const hash = reader.readBytes(32) - const sequence = reader.readU32() - createdAt = { hash, sequence } + if (decoder === undefined) { + throw new DecodeInvalid(`Encoded account version ${version} not supported.`) } - const hasMultiSigKeys = reader.readU8() === 1 - - if (hasMultiSigKeys) { - multiSigKeys = { - identifier: reader.readVarBytes().toString('hex'), - keyPackage: reader.readVarBytes().toString('hex'), - proofGenerationKey: reader.readVarBytes().toString('hex'), - } - } + return decoder(reader, options) } catch (e) { if (e instanceof EncodingError) { throw new DecodeFailed( @@ -110,18 +87,6 @@ export class Bech32Encoder implements AccountEncoder { } throw e } - - return { - version: ACCOUNT_SCHEMA_VERSION, - name: options?.name ? options.name : name, - viewKey, - incomingViewKey, - outgoingViewKey, - spendingKey, - publicAddress, - createdAt, - multiSigKeys, - } } getSize(value: AccountImport): number { @@ -151,3 +116,60 @@ export class Bech32Encoder implements AccountEncoder { return size } } + +function decoderV1( + reader: bufio.BufferReader, + options?: AccountDecodingOptions, +): AccountImport { + const name = reader.readVarString('utf8') + const viewKey = reader.readBytes(VIEW_KEY_LENGTH).toString('hex') + const incomingViewKey = reader.readBytes(KEY_LENGTH).toString('hex') + const outgoingViewKey = reader.readBytes(KEY_LENGTH).toString('hex') + const publicAddress = reader.readBytes(PUBLIC_ADDRESS_LENGTH).toString('hex') + + const hasSpendingKey = reader.readU8() === 1 + const spendingKey = hasSpendingKey ? reader.readBytes(KEY_LENGTH).toString('hex') : null + + const hasCreatedAt = reader.readU8() === 1 + + let createdAt = null + if (hasCreatedAt) { + const hash = reader.readBytes(32) + const sequence = reader.readU32() + createdAt = { hash, sequence } + } + + return { + version: ACCOUNT_SCHEMA_VERSION, + name: options?.name ? options.name : name, + viewKey, + incomingViewKey, + outgoingViewKey, + spendingKey, + publicAddress, + createdAt, + } +} + +function decoderV2( + reader: bufio.BufferReader, + options?: AccountDecodingOptions, +): AccountImport { + const accountImport = decoderV1(reader, options) + + let multiSigKeys = undefined + + const hasMultiSigKeys = reader.readU8() === 1 + if (hasMultiSigKeys) { + multiSigKeys = { + identifier: reader.readVarBytes().toString('hex'), + keyPackage: reader.readVarBytes().toString('hex'), + proofGenerationKey: reader.readVarBytes().toString('hex'), + } + } + + return { + ...accountImport, + multiSigKeys, + } +} From 517e7028991798d6b7c72c9965bdb95fa0680a3a Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Wed, 31 Jan 2024 18:09:17 -0800 Subject: [PATCH 27/27] bumps versions for sdk v1.19.0 (#4656) --- ironfish-cli/package.json | 6 +++--- ironfish-rust-nodejs/npm/darwin-arm64/package.json | 2 +- ironfish-rust-nodejs/npm/darwin-x64/package.json | 2 +- ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json | 2 +- ironfish-rust-nodejs/npm/linux-arm64-musl/package.json | 2 +- ironfish-rust-nodejs/npm/linux-x64-gnu/package.json | 2 +- ironfish-rust-nodejs/npm/linux-x64-musl/package.json | 2 +- ironfish-rust-nodejs/npm/win32-x64-msvc/package.json | 2 +- ironfish-rust-nodejs/package.json | 2 +- ironfish/package.json | 4 ++-- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ironfish-cli/package.json b/ironfish-cli/package.json index 16d42a6a3f..0c0cfed077 100644 --- a/ironfish-cli/package.json +++ b/ironfish-cli/package.json @@ -1,6 +1,6 @@ { "name": "ironfish", - "version": "1.18.0", + "version": "1.19.0", "description": "CLI for running and interacting with an Iron Fish node", "author": "Iron Fish (https://ironfish.network)", "main": "build/src/index.js", @@ -62,8 +62,8 @@ "@aws-sdk/client-s3": "3", "@aws-sdk/client-secrets-manager": "3", "@aws-sdk/s3-request-presigner": "3", - "@ironfish/rust-nodejs": "1.15.0", - "@ironfish/sdk": "1.18.0", + "@ironfish/rust-nodejs": "1.16.0", + "@ironfish/sdk": "1.19.0", "@oclif/core": "1.23.1", "@oclif/plugin-help": "5.1.12", "@oclif/plugin-not-found": "2.3.1", diff --git a/ironfish-rust-nodejs/npm/darwin-arm64/package.json b/ironfish-rust-nodejs/npm/darwin-arm64/package.json index e2b8d02a8b..f89ca60e89 100644 --- a/ironfish-rust-nodejs/npm/darwin-arm64/package.json +++ b/ironfish-rust-nodejs/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-darwin-arm64", - "version": "1.15.0", + "version": "1.16.0", "os": [ "darwin" ], diff --git a/ironfish-rust-nodejs/npm/darwin-x64/package.json b/ironfish-rust-nodejs/npm/darwin-x64/package.json index 68c4e473e8..6d9f5fb41e 100644 --- a/ironfish-rust-nodejs/npm/darwin-x64/package.json +++ b/ironfish-rust-nodejs/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-darwin-x64", - "version": "1.15.0", + "version": "1.16.0", "os": [ "darwin" ], diff --git a/ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json b/ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json index 853505b461..57f666f6c8 100644 --- a/ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json +++ b/ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-linux-arm64-gnu", - "version": "1.15.0", + "version": "1.16.0", "os": [ "linux" ], diff --git a/ironfish-rust-nodejs/npm/linux-arm64-musl/package.json b/ironfish-rust-nodejs/npm/linux-arm64-musl/package.json index 590c237813..e480b3c4ed 100644 --- a/ironfish-rust-nodejs/npm/linux-arm64-musl/package.json +++ b/ironfish-rust-nodejs/npm/linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-linux-arm64-musl", - "version": "1.15.0", + "version": "1.16.0", "os": [ "linux" ], diff --git a/ironfish-rust-nodejs/npm/linux-x64-gnu/package.json b/ironfish-rust-nodejs/npm/linux-x64-gnu/package.json index 215b57f361..27f2442cec 100644 --- a/ironfish-rust-nodejs/npm/linux-x64-gnu/package.json +++ b/ironfish-rust-nodejs/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-linux-x64-gnu", - "version": "1.15.0", + "version": "1.16.0", "os": [ "linux" ], diff --git a/ironfish-rust-nodejs/npm/linux-x64-musl/package.json b/ironfish-rust-nodejs/npm/linux-x64-musl/package.json index 64d1da1714..439736d5d9 100644 --- a/ironfish-rust-nodejs/npm/linux-x64-musl/package.json +++ b/ironfish-rust-nodejs/npm/linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-linux-x64-musl", - "version": "1.15.0", + "version": "1.16.0", "os": [ "linux" ], diff --git a/ironfish-rust-nodejs/npm/win32-x64-msvc/package.json b/ironfish-rust-nodejs/npm/win32-x64-msvc/package.json index 61f9afa9eb..11649ce453 100644 --- a/ironfish-rust-nodejs/npm/win32-x64-msvc/package.json +++ b/ironfish-rust-nodejs/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-win32-x64-msvc", - "version": "1.15.0", + "version": "1.16.0", "os": [ "win32" ], diff --git a/ironfish-rust-nodejs/package.json b/ironfish-rust-nodejs/package.json index dbae5b3e25..444d831f1e 100644 --- a/ironfish-rust-nodejs/package.json +++ b/ironfish-rust-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs", - "version": "1.15.0", + "version": "1.16.0", "description": "Node.js bindings for Rust code required by the Iron Fish SDK", "main": "index.js", "types": "index.d.ts", diff --git a/ironfish/package.json b/ironfish/package.json index 5d8ab981c0..09147f1697 100644 --- a/ironfish/package.json +++ b/ironfish/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/sdk", - "version": "1.18.0", + "version": "1.19.0", "description": "SDK for running and interacting with an Iron Fish node", "author": "Iron Fish (https://ironfish.network)", "main": "build/src/index.js", @@ -22,7 +22,7 @@ "dependencies": { "@ethersproject/bignumber": "5.7.0", "@fast-csv/format": "4.3.5", - "@ironfish/rust-nodejs": "1.15.0", + "@ironfish/rust-nodejs": "1.16.0", "@napi-rs/blake-hash": "1.3.3", "axios": "0.21.4", "bech32": "2.0.0",