diff --git a/ironfish/src/rpc/routes/chain/serializers.ts b/ironfish/src/rpc/routes/chain/serializers.ts index 04490c16a6..ce4dc25ce6 100644 --- a/ironfish/src/rpc/routes/chain/serializers.ts +++ b/ironfish/src/rpc/routes/chain/serializers.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { getBlockSize, getTransactionSize } from '../../../network/utils/serializers' -import { Block, BlockHeader, Transaction } from '../../../primitives' +import { Block, BlockHeader, Target, Transaction } from '../../../primitives' import { BufferUtils } from '../../../utils' import { RpcBlock, RpcBlockHeader, RpcTransaction } from './types' @@ -25,6 +25,22 @@ export function serializeRpcBlockHeader(header: BlockHeader): RpcBlockHeader { } } +export function deserializeRpcBlockHeader(header: RpcBlockHeader): BlockHeader { + return new BlockHeader( + { + sequence: header.sequence, + previousBlockHash: Buffer.from(header.previousBlockHash, 'hex'), + noteCommitment: Buffer.from(header.noteCommitment, 'hex'), + transactionCommitment: Buffer.from(header.transactionCommitment, 'hex'), + target: new Target(header.target), + randomness: BigInt(header.randomness), + timestamp: new Date(header.timestamp), + graffiti: Buffer.from(header.graffiti, 'hex'), + }, + Buffer.from(header.hash, 'hex'), + ) +} + export const serializeRpcBlock = (block: Block, serialized?: boolean): RpcBlock => { const blockHeaderResponse = serializeRpcBlockHeader(block.header) @@ -79,3 +95,13 @@ export const serializeRpcTransaction = ( ...(serialized ? { serialized: tx.serialize().toString('hex') } : {}), } } + +export const deserializeRpcTransaction = (tx: RpcTransaction): Transaction => { + if (tx.serialized) { + return new Transaction(Buffer.from(tx.serialized, 'hex')) + } + + throw new Error( + `Deserializing transactions that are not serialized are currently not supported.`, + ) +} diff --git a/ironfish/src/wallet/account/account.ts b/ironfish/src/wallet/account/account.ts index ea2e6d2f9f..dc2f8d14db 100644 --- a/ironfish/src/wallet/account/account.ts +++ b/ironfish/src/wallet/account/account.ts @@ -6,7 +6,7 @@ import { Asset } from '@ironfish/rust-nodejs' import { BufferMap, BufferSet } from 'buffer-map' import MurmurHash3 from 'imurmurhash' import { Assert } from '../../assert' -import { Transaction } from '../../primitives' +import { BlockHeader, Transaction } from '../../primitives' import { GENESIS_BLOCK_SEQUENCE } from '../../primitives/block' import { Note } from '../../primitives/note' import { DatabaseKeyRange, IDatabaseTransaction } from '../../storage' @@ -15,7 +15,6 @@ import { WithNonNull, WithRequired } from '../../utils' import { DecryptedNote } from '../../workerPool/tasks/decryptNotes' import { AssetBalances } from '../assetBalances' import { MultisigKeys, MultisigSigner } from '../interfaces/multisigKeys' -import { WalletBlockHeader } from '../scanner/remoteChainProcessor' import { AccountValue } from '../walletdb/accountValue' import { AssetValue } from '../walletdb/assetValue' import { BalanceValue } from '../walletdb/balanceValue' @@ -174,7 +173,7 @@ export class Account { } async connectTransaction( - blockHeader: WalletBlockHeader, + blockHeader: BlockHeader, transaction: Transaction, decryptedNotes: Array, tx?: IDatabaseTransaction, @@ -488,7 +487,7 @@ export class Account { } private async deleteDisconnectedMintsFromAssetsStore( - blockHeader: WalletBlockHeader, + blockHeader: BlockHeader, transaction: Transaction, receivedAssets: BufferSet | null, tx: IDatabaseTransaction, @@ -643,7 +642,7 @@ export class Account { } async disconnectTransaction( - blockHeader: WalletBlockHeader, + blockHeader: BlockHeader, transaction: Transaction, tx?: IDatabaseTransaction, ): Promise { diff --git a/ironfish/src/wallet/scanner/remoteChainProcessor.ts b/ironfish/src/wallet/scanner/remoteChainProcessor.ts index 5fa8977a54..7da6bd1f1e 100644 --- a/ironfish/src/wallet/scanner/remoteChainProcessor.ts +++ b/ironfish/src/wallet/scanner/remoteChainProcessor.ts @@ -4,22 +4,14 @@ import { Assert } from '../../assert' import { Event } from '../../event' import { Logger } from '../../logger' -import { Transaction } from '../../primitives' -import { FollowChainStreamResponse, RpcClient } from '../../rpc' +import { BlockHeader, Transaction } from '../../primitives' +import { RpcClient } from '../../rpc' +import { + deserializeRpcBlockHeader, + deserializeRpcTransaction, +} from '../../rpc/routes/chain/serializers' import { BufferUtils } from '../../utils' -export type WalletBlockHeader = { - hash: Buffer - previousBlockHash: Buffer - sequence: number - timestamp: Date -} - -export type WalletBlockTransaction = { - transaction: Transaction - initialNoteIndex: number -} - export class RemoteChainProcessor { hash: Buffer | null = null sequence: number | null = null @@ -27,10 +19,28 @@ export class RemoteChainProcessor { nodeClient: RpcClient | null maxQueueSize: number - onAdd = new Event<[{ header: WalletBlockHeader; transactions: WalletBlockTransaction[] }]>() + onAdd = new Event< + [ + { + header: BlockHeader + transactions: { + transaction: Transaction + initialNoteIndex: number + }[] + }, + ] + >() onRemove = new Event< - [{ header: WalletBlockHeader; transactions: WalletBlockTransaction[] }] + [ + { + header: BlockHeader + transactions: { + transaction: Transaction + initialNoteIndex: number + }[] + }, + ] >() constructor(options: { @@ -68,49 +78,47 @@ export class RemoteChainProcessor { continue } - const blockHeader: WalletBlockHeader = { - hash: Buffer.from(block.hash, 'hex'), - previousBlockHash: Buffer.from(block.previousBlockHash, 'hex'), - sequence: block.sequence, - timestamp: new Date(block.timestamp), - } + const header = deserializeRpcBlockHeader(block) - const blockTransactions = this.getBlockTransactions(content) + const transactions = this.mapTransactionWithNoteIndex( + header, + block.transactions.map((tx) => deserializeRpcTransaction(tx)), + ) if (type === 'connected') { - this.hash = blockHeader.hash - this.sequence = blockHeader.sequence - await this.onAdd.emitAsync({ header: blockHeader, transactions: blockTransactions }) + this.hash = header.hash + this.sequence = header.sequence + + await this.onAdd.emitAsync({ header, transactions }) } else if (type === 'disconnected') { - this.hash = blockHeader.previousBlockHash - this.sequence = blockHeader.sequence - 1 + this.hash = header.previousBlockHash + this.sequence = header.sequence - 1 + await this.onRemove.emitAsync({ - header: blockHeader, - transactions: blockTransactions, + header: header, + transactions: transactions, }) } } - return { hashChanged: !BufferUtils.equalsNullable(this.hash, oldHash) } + const hashChanged = !BufferUtils.equalsNullable(this.hash, oldHash) + return { hashChanged } } - getBlockTransactions(response: FollowChainStreamResponse): WalletBlockTransaction[] { - const transactions = [] + mapTransactionWithNoteIndex( + header: BlockHeader, + transactions: Transaction[], + ): Array<{ transaction: Transaction; initialNoteIndex: number }> { + Assert.isNotNull(header.noteSize) + let initialNoteIndex = header.noteSize - Assert.isNotNull(response.block.noteSize) - let initialNoteIndex = response.block.noteSize + const result = [] - for (const rpcTransaction of response.block.transactions.slice().reverse()) { - Assert.isNotUndefined(rpcTransaction.serialized) - const transaction = new Transaction(Buffer.from(rpcTransaction.serialized, 'hex')) + for (const transaction of transactions.slice().reverse()) { initialNoteIndex -= transaction.notes.length - - transactions.push({ - transaction, - initialNoteIndex, - }) + result.push({ transaction, initialNoteIndex }) } - return transactions.slice().reverse() + return result.slice().reverse() } } diff --git a/ironfish/src/wallet/scanner/scanState.ts b/ironfish/src/wallet/scanner/scanState.ts index 07ddfb171d..2d4864ec84 100644 --- a/ironfish/src/wallet/scanner/scanState.ts +++ b/ironfish/src/wallet/scanner/scanState.ts @@ -4,9 +4,9 @@ import { Event } from '../../event' import { Meter } from '../../metrics' +import { BlockHeader } from '../../primitives' import { PromiseResolve, PromiseUtils } from '../../utils' import { HeadValue } from '../walletdb/headValue' -import { WalletBlockHeader } from './remoteChainProcessor' export class ScanState { hash: Buffer | null = null @@ -43,7 +43,7 @@ export class ScanState { return (remaining / this.speed.rate1m) * 1000 } - signal(header: WalletBlockHeader): void { + signal(header: BlockHeader): void { this.hash = header.hash this.sequence = header.sequence this.speed.add(1) diff --git a/ironfish/src/wallet/scanner/walletScanner.ts b/ironfish/src/wallet/scanner/walletScanner.ts index ec67c27065..4ba652cd6e 100644 --- a/ironfish/src/wallet/scanner/walletScanner.ts +++ b/ironfish/src/wallet/scanner/walletScanner.ts @@ -7,14 +7,11 @@ import { BufferMap } from 'buffer-map' import { Config } from '../../fileStores' import { Logger } from '../../logger' import { Mutex } from '../../mutex' +import { BlockHeader, Transaction } from '../../primitives' import { RpcClient } from '../../rpc' import { AsyncUtils, BufferUtils, HashUtils } from '../../utils' import { DecryptedNote } from '../../workerPool/tasks/decryptNotes' -import { - RemoteChainProcessor, - WalletBlockHeader, - WalletBlockTransaction, -} from './remoteChainProcessor' +import { RemoteChainProcessor } from './remoteChainProcessor' import { ScanState } from './scanState' export class WalletScanner { @@ -127,8 +124,8 @@ export class WalletScanner { } async connectBlock( - blockHeader: WalletBlockHeader, - transactions: WalletBlockTransaction[], + blockHeader: BlockHeader, + transactions: { transaction: Transaction; initialNoteIndex: number }[], abort?: AbortController, ): Promise { if (blockHeader.sequence % 100 === 0) { @@ -202,8 +199,8 @@ export class WalletScanner { } async disconnectBlock( - header: WalletBlockHeader, - transactions: WalletBlockTransaction[], + header: BlockHeader, + transactions: { transaction: Transaction; initialNoteIndex: number }[], abort?: AbortController, ): Promise { this.logger.debug(`AccountHead DEL: ${header.sequence} => ${Number(header.sequence) - 1}`) diff --git a/ironfish/src/wallet/wallet.ts b/ironfish/src/wallet/wallet.ts index b332c1c9f7..3fee8791a2 100644 --- a/ironfish/src/wallet/wallet.ts +++ b/ironfish/src/wallet/wallet.ts @@ -21,6 +21,7 @@ import { NoteHasher } from '../merkletree' import { Side } from '../merkletree/merkletree' import { Witness } from '../merkletree/witness' import { Mutex } from '../mutex' +import { BlockHeader } from '../primitives' import { GENESIS_BLOCK_PREVIOUS, GENESIS_BLOCK_SEQUENCE } from '../primitives/block' import { BurnDescription } from '../primitives/burnDescription' import { MintDescription } from '../primitives/mintDescription' @@ -50,7 +51,6 @@ import { import { AccountImport } from './exporter/accountImport' import { isMultisigSignerTrustedDealerImport } from './exporter/multisig' import { MintAssetOptions } from './interfaces/mintAssetOptions' -import { WalletBlockHeader, WalletBlockTransaction } from './scanner/remoteChainProcessor' import { ScanState } from './scanner/scanState' import { WalletScanner } from './scanner/walletScanner' import { validateAccount } from './validator' @@ -431,7 +431,7 @@ export class Wallet { async connectBlockForAccount( account: Account, - blockHeader: WalletBlockHeader, + blockHeader: BlockHeader, transactions: { transaction: Transaction; decryptedNotes: DecryptedNote[] }[], shouldDecrypt: boolean, ): Promise { @@ -466,10 +466,7 @@ export class Wallet { }) } - async shouldDecryptForAccount( - blockHeader: WalletBlockHeader, - account: Account, - ): Promise { + async shouldDecryptForAccount(blockHeader: BlockHeader, account: Account): Promise { if (account.createdAt === null) { return true } @@ -495,7 +492,7 @@ export class Wallet { } private async connectBlockTransactions( - blockHeader: WalletBlockHeader, + blockHeader: BlockHeader, transactions: Array<{ transaction: Transaction; decryptedNotes: Array }>, account: Account, tx?: IDatabaseTransaction, @@ -521,7 +518,7 @@ export class Wallet { private async upsertAssetsFromDecryptedNotes( account: Account, decryptedNotes: DecryptedNote[], - blockHeader: WalletBlockHeader, + blockHeader: BlockHeader, tx?: IDatabaseTransaction, ): Promise { for (const { serializedNote } of decryptedNotes) { @@ -605,8 +602,8 @@ export class Wallet { async disconnectBlockForAccount( account: Account, - header: WalletBlockHeader, - transactions: WalletBlockTransaction[], + header: BlockHeader, + transactions: { transaction: Transaction; initialNoteIndex: number }[], ) { const assetBalanceDeltas = new AssetBalances()