From: Chris Duncan Date: Wed, 6 Aug 2025 04:37:39 +0000 (-0700) Subject: Refactor Block to be a concrete class instead of abstract. X-Git-Tag: v0.10.5~43^2~74 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=bbe64ef7de730dada9b4e174c786ccb9a9a0d814;p=libnemo.git Refactor Block to be a concrete class instead of abstract. --- diff --git a/src/lib/block.ts b/src/lib/block.ts index 7b5571e..0bf814c 100644 --- a/src/lib/block.ts +++ b/src/lib/block.ts @@ -15,28 +15,34 @@ import { Wallet } from './wallet' * class is abstract and cannot be directly instantiated. Every block must be one * of three derived classes: SendBlock, ReceiveBlock, ChangeBlock. */ -abstract class Block { +class Block { + subtype?: 'send' | 'receive' | 'change' account: Account - type: 'state' = 'state' - abstract subtype?: 'send' | 'receive' | 'change' - abstract previous: string - abstract representative?: Account - abstract balance: bigint - abstract link: string - abstract signature?: string - abstract work?: string + balance: bigint + previous: string + representative: Account + link?: string + signature?: string + work?: string - constructor (account: Account | string) { - if (this.constructor === Block) { - throw new Error('Block is an abstract class and cannot be instantiated directly.') - } - if (account instanceof Account) { - this.account = account - } else if (typeof account === 'string') { - this.account = Account.import(account) - } else { + constructor (account: Account) + constructor (account: unknown) { + if (!(account instanceof Account)) { throw new TypeError('Invalid account') } + if (account.balance == null) { + throw new TypeError('Account balance is unknown') + } + if (account.frontier == null) { + throw new TypeError('Account frontier is unknown') + } + if (account.representative == null) { + throw new TypeError('Account representative is unknown') + } + this.account = account + this.balance = account.balance + this.previous = account.frontier + this.representative = account.representative } /** @@ -45,20 +51,25 @@ abstract class Block { * @returns {Promise} Block data hashed to a byte array */ get hash (): string { - if (this.representative == null) { - throw new Error('Block hash missing representative') + try { + if (this.link == null) { + throw new Error('Block link is required') + } + const data = [ + PREAMBLE, + this.account.publicKey, + this.previous.padStart(64, '0'), + this.representative.publicKey, + dec.toHex(this.balance, 32), + this.link.padStart(64, '0') + ] + const hash = new Blake2b(32) + data.forEach(str => hash.update(hex.toBytes(str))) + return hash.digest('hex').toUpperCase() + } catch (err) { + console.error(err) + throw new Error('Failed to hash block', { cause: err }) } - const data = [ - PREAMBLE, - this.account.publicKey, - this.previous.padStart(64, '0'), - this.representative.publicKey, - dec.toHex(this.balance, 32), - this.link.padStart(64, '0') - ] - const hash = new Blake2b(32) - data.forEach(str => hash.update(hex.toBytes(str))) - return hash.digest('hex').toUpperCase() } /** @@ -67,15 +78,23 @@ abstract class Block { * @returns {string} JSON representation of the block */ toJSON (): { [key: string]: string } { - return { - "type": this.type, - "account": this.account.address, - "previous": this.previous, - "representative": this.representative?.address ?? '', - "balance": this.balance.toString(), - "link": this.link, - "signature": this.signature ?? '', - "work": this.work ?? '' + try { + if (this.link == null) { + throw new Error('Block link is required') + } + return { + "type": "state", + "account": this.account.address, + "previous": this.previous, + "representative": this.representative.address ?? '', + "balance": this.balance.toString(), + "link": this.link, + "signature": this.signature ?? '', + "work": this.work ?? '' + } + } catch (err) { + console.error(err) + throw new Error('Failed to stringify block', { cause: err }) } } @@ -90,9 +109,11 @@ abstract class Block { } try { this.subtype = 'change' + this.link = Account.import(BURN_ADDRESS).publicKey return this } catch (err) { this.subtype = undefined + this.link = undefined throw new TypeError('Failed to configure send block', { cause: err }) } } @@ -122,7 +143,6 @@ abstract class Block { this.link = (typeof send === 'string') ? send : send.hash - this.representative = this.account.representative return this } catch (err) { throw new Error('Failed to receive a corresponding send', { cause: err }) @@ -135,7 +155,7 @@ abstract class Block { * A successful response sets the `work` property. */ async pow (): Promise { - const difficulty: bigint = (this instanceof SendBlock || this instanceof ChangeBlock) + const difficulty: bigint = (this.subtype === 'send' || this.subtype === 'change') ? DIFFICULTY_SEND : DIFFICULTY_RECEIVE const result = await NanoPow.work_generate(this.previous, { difficulty }) @@ -163,6 +183,7 @@ abstract class Block { * @returns {Promise} Hash of the processed block */ async process (rpc: Rpc): Promise { + validate(this) if (!this.signature) { throw new Error('Block is missing signature. Use sign() and try again.') } @@ -325,7 +346,8 @@ abstract class Block { return this } else if (input instanceof Wallet && typeof param === 'number') { - const sig = await input.sign(param, this as SendBlock | ReceiveBlock | ChangeBlock) + const wallet = input + const sig = await wallet.sign(param, this) if (this.signature !== sig) { throw new Error('Wallet signature does not match block signature') } @@ -336,14 +358,14 @@ abstract class Block { const { Ledger } = await import('./ledger') const ledger = await Ledger.create() await ledger.connect() - if (param && (param instanceof ChangeBlock || param instanceof ReceiveBlock || param instanceof SendBlock)) { + if (param && param instanceof Block) { try { await ledger.updateCache(index, param) } catch (err) { console.warn('Error updating Ledger cache of previous block, attempting signature anyway', err) } } - this.signature = await ledger.sign(index, this as SendBlock | ReceiveBlock | ChangeBlock) + this.signature = await ledger.sign(index, this) return this } else { @@ -379,7 +401,6 @@ abstract class Block { } switch (this.subtype) { case 'change': { - this.link = Account.import(BURN_ADDRESS).publicKey this.representative = (typeof account === 'string') ? Account.import(account) : account @@ -389,7 +410,6 @@ abstract class Block { this.link = (typeof account === 'string') ? Account.import(account).publicKey : account.publicKey - this.representative = this.account.representative break } default: { @@ -422,118 +442,6 @@ abstract class Block { } } -/** -* Represents a block that sends funds from one address to another as defined by -* the Nano cryptocurrency protocol. -*/ -export class SendBlock extends Block { - type: 'state' = 'state' - subtype: 'send' = 'send' - previous: string - representative: Account - balance: bigint - link: string - signature?: string - work?: string - - constructor ( - sender: Account | string, - balance: string, - recipient: string, - amount: string, - representative: Account | string, - frontier: string, - work?: string - ) { - if (typeof sender === 'string') sender = Account.import(sender) - if (typeof representative === 'string') representative = Account.import(representative) - super(sender) - this.previous = frontier - this.representative = representative - this.link = Account.import(recipient).publicKey - this.work = work ?? '' - - const bigBalance = BigInt(balance) - const bigAmount = BigInt(amount) - this.balance = bigBalance - bigAmount - - validate(this) - } -} - -/** -* Represents a block that receives funds sent to one address from another as -* defined by the Nano cryptocurrency protocol. -*/ -export class ReceiveBlock extends Block { - type: 'state' = 'state' - subtype: 'receive' = 'receive' - previous: string - representative: Account - balance: bigint - link: string - signature?: string - work?: string - - constructor ( - recipient: Account | string, - balance: string, - origin: string, - amount: string, - representative: Account | string, - frontier?: string, - work?: string - ) { - if (typeof recipient === 'string') recipient = Account.import(recipient) - if (typeof representative === 'string') representative = Account.import(representative) - super(recipient) - this.previous = frontier ?? recipient.publicKey - this.representative = representative - this.link = origin - this.work = work ?? '' - - const bigBalance = BigInt(balance) - const bigAmount = BigInt(amount) - this.balance = bigBalance + bigAmount - - validate(this) - } -} - -/** -* Represents a block that changes the representative account to which the user -* account delegates their vote weight using the the Open Representative Voting -* specification as defined by the Nano cryptocurrency protocol. -*/ -export class ChangeBlock extends Block { - type: 'state' = 'state' - subtype: 'change' = 'change' - previous: string - representative: Account - balance: bigint - link: string = Account.import(BURN_ADDRESS).publicKey - signature?: string - work?: string - - constructor ( - account: Account | string, - balance: string, - representative: Account | string, - frontier: string, - work?: string - ) { - if (typeof account === 'string') account = Account.import(account) - if (typeof representative === 'string') representative = Account.import(representative) - super(account) - this.previous = frontier - this.representative = representative - this.balance = BigInt(balance) - this.work = work ?? '' - - validate(this) - } -} - /** * Validates block data. * @@ -562,17 +470,17 @@ function validate (block: unknown): asserts block is Block { if (b.balance < 0) { throw new Error('Negative balance') } - if (b instanceof SendBlock) { + if (b.subtype === 'send') { if (b.link == null || b.link === '') { throw new Error('Recipient missing') } } - if (b instanceof ReceiveBlock) { + if (b.subtype === 'receive') { if (b.link == null) { throw new Error('Origin send block hash missing') } } - if (b instanceof ChangeBlock) { + if (b.subtype === 'change') { if (b.link == null) { throw new Error('Change block link missing') }