* 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
}
/**
* @returns {Promise<string>} 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()
}
/**
* @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 })
}
}
}
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 })
}
}
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 })
* A successful response sets the `work` property.
*/
async pow (): Promise<Block> {
- 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 })
* @returns {Promise<string>} Hash of the processed block
*/
async process (rpc: Rpc): Promise<string> {
+ validate(this)
if (!this.signature) {
throw new Error('Block is missing signature. Use sign() and try again.')
}
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')
}
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 {
}
switch (this.subtype) {
case 'change': {
- this.link = Account.import(BURN_ADDRESS).publicKey
this.representative = (typeof account === 'string')
? Account.import(account)
: account
this.link = (typeof account === 'string')
? Account.import(account).publicKey
: account.publicKey
- this.representative = this.account.representative
break
}
default: {
}
}
-/**
-* 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.
*
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')
}