diff --git a/src/api/v1/chain.ts b/src/api/v1/chain.ts index abc1dff..c197e3d 100644 --- a/src/api/v1/chain.ts +++ b/src/api/v1/chain.ts @@ -1,6 +1,7 @@ import {APIClient} from '../client' import { + BlockIdType, Bytes, Checksum160, Checksum256, @@ -81,7 +82,7 @@ export class ChainAPI { }) } - async get_block(block_num_or_id: Checksum256Type | UInt32Type) { + async get_block(block_num_or_id: BlockIdType | UInt32Type) { return this.client.call({ path: '/v1/chain/get_block', params: {block_num_or_id}, @@ -89,7 +90,7 @@ export class ChainAPI { }) } - async get_block_header_state(block_num_or_id: Checksum256Type | UInt32Type) { + async get_block_header_state(block_num_or_id: BlockIdType | UInt32Type) { return this.client.call({ path: '/v1/chain/get_block_header_state', params: {block_num_or_id}, diff --git a/src/api/v1/types.ts b/src/api/v1/types.ts index 35d6dc3..f349ff0 100644 --- a/src/api/v1/types.ts +++ b/src/api/v1/types.ts @@ -3,6 +3,7 @@ import { AnyAction, Asset, Authority, + BlockId, BlockTimestamp, Bytes, Checksum160, @@ -252,7 +253,7 @@ export class GetBlockResponse extends Struct { @Struct.field('time_point') declare timestamp: TimePoint @Struct.field('name') declare producer: Name @Struct.field('uint16') declare confirmed: UInt16 - @Struct.field('checksum256') declare previous: Checksum256 + @Struct.field(BlockId) declare previous: BlockId @Struct.field('checksum256') declare transaction_mroot: Checksum256 @Struct.field('checksum256') declare action_mroot: Checksum256 @Struct.field('uint32') declare schedule_version: UInt32 @@ -263,7 +264,7 @@ export class GetBlockResponse extends Struct { @Struct.field(GetBlockResponseTransactionReceipt, {array: true}) declare transactions: GetBlockResponseTransactionReceipt[] @Struct.field('block_extension', {optional: true}) declare block_extensions: BlockExtension[] - @Struct.field('checksum256') declare id: Checksum256 + @Struct.field(BlockId) declare id: BlockId @Struct.field('uint32') declare block_num: UInt32 @Struct.field('uint32') declare ref_block_prefix: UInt32 } @@ -292,7 +293,7 @@ export class BlockStateHeader extends Struct { @Struct.field('time_point') declare timestamp: TimePoint @Struct.field('name') declare producer: Name @Struct.field('uint16') declare confirmed: UInt16 - @Struct.field('checksum256') declare previous: Checksum256 + @Struct.field(BlockId) declare previous: BlockId @Struct.field('checksum256') declare transaction_mroot: Checksum256 @Struct.field('checksum256') declare action_mroot: Checksum256 @Struct.field('uint32') declare schedule_version: UInt32 @@ -306,7 +307,7 @@ export class GetBlockHeaderStateResponse extends Struct { @Struct.field('uint32') declare block_num: UInt32 @Struct.field('uint32') declare dpos_proposed_irreversible_blocknum: UInt32 @Struct.field('uint32') declare dpos_irreversible_blocknum: UInt32 - @Struct.field('checksum256') declare id: Checksum256 + @Struct.field(BlockId) declare id: BlockId @Struct.field(BlockStateHeader) declare header: BlockStateHeader /** Unstructured any fields specific to header state calls */ @Struct.field('any') declare active_schedule: any @@ -331,9 +332,9 @@ export class GetInfoResponse extends Struct { /** Highest block number on the chain that has been irreversibly applied to state. */ @Struct.field('uint32') declare last_irreversible_block_num: UInt32 /** Highest block ID on the chain that has been irreversibly applied to state. */ - @Struct.field('checksum256') declare last_irreversible_block_id: Checksum256 + @Struct.field(BlockId) declare last_irreversible_block_id: BlockId /** Highest block ID on the chain. */ - @Struct.field('checksum256') declare head_block_id: Checksum256 + @Struct.field(BlockId) declare head_block_id: BlockId /** Highest block unix timestamp. */ @Struct.field('time_point') declare head_block_time: TimePoint /** Producer that signed the highest block (head block). */ @@ -351,7 +352,7 @@ export class GetInfoResponse extends Struct { /** Sequential block number representing the best known head in the fork database tree. */ @Struct.field('uint32?') fork_db_head_block_num?: UInt32 /** Hash representing the best known head in the fork database tree. */ - @Struct.field('checksum256?') fork_db_head_block_id?: Checksum256 + @Struct.field('block_id_type?') fork_db_head_block_id?: BlockId getTransactionHeader(secondsAhead = 120): TransactionHeader { const expiration = TimePointSec.fromMilliseconds( @@ -600,12 +601,12 @@ export class GetControlledAccountsResponse extends Struct { export class GetTransactionStatusResponse extends Struct { @Struct.field('string') declare state: string @Struct.field('uint32') declare head_number: UInt32 - @Struct.field('checksum256') declare head_id: Checksum256 + @Struct.field(BlockId) declare head_id: BlockId @Struct.field('time_point') declare head_timestamp: TimePoint @Struct.field('uint32') declare irreversible_number: UInt32 - @Struct.field('checksum256') declare irreversible_id: Checksum256 + @Struct.field(BlockId) declare irreversible_id: BlockId @Struct.field('time_point') declare irreversible_timestamp: TimePoint - @Struct.field('checksum256') declare earliest_tracked_block_id: Checksum256 + @Struct.field(BlockId) declare earliest_tracked_block_id: BlockId @Struct.field('uint32') declare earliest_tracked_block_number: UInt32 } diff --git a/src/chain/block-id.ts b/src/chain/block-id.ts new file mode 100644 index 0000000..afa3f10 --- /dev/null +++ b/src/chain/block-id.ts @@ -0,0 +1,78 @@ +import {Bytes, BytesType, Checksum256, Checksum256Type, isInstanceOf, UInt32, UInt32Type} from '..' +import {ABIDecoder, ABIEncoder, ABISerializableObject} from '../serializer' +import {arrayEquals, arrayToHex} from '../utils' + +export type BlockIdType = BlockId | BytesType | {blockNum: UInt32Type; checksum: Checksum256Type} + +export class BlockId implements ABISerializableObject { + static abiName = 'block_id_type' // eosio contract context defines this with a _type suffix for some reason + + static from(value: BlockIdType) { + if (isInstanceOf(value, this)) { + return value + } + if (Bytes.isBytes(value)) { + return new this(Bytes.from(value).array) + } else { + return this.fromBlockChecksum(value.checksum, value.blockNum) + } + } + + static fromABI(decoder: ABIDecoder) { + return new this(decoder.readArray(32)) + } + + static fromBlockChecksum(checksum: Checksum256Type, blockNum: UInt32Type): BlockId { + const id = new BlockId(Checksum256.from(checksum).array) + const numBuffer = new Uint8Array(4) + numBuffer[0] = (Number(blockNum) >> 24) & 0xff + numBuffer[1] = (Number(blockNum) >> 16) & 0xff + numBuffer[2] = (Number(blockNum) >> 8) & 0xff + numBuffer[3] = Number(blockNum) & 0xff + id.array.set(numBuffer, 0) + return id + } + + readonly array: Uint8Array + + constructor(array: Uint8Array) { + if (array.byteLength !== 32) { + throw new Error(`BlockId size mismatch, expected 32 bytes got ${array.byteLength}`) + } + this.array = array + } + + equals(other: BlockIdType): boolean { + const self = this.constructor as typeof BlockId + try { + return arrayEquals(this.array, self.from(other).array) + } catch { + return false + } + } + + toABI(encoder: ABIEncoder) { + encoder.writeArray(this.array) + } + + toString() { + return this.hexString + } + + toJSON() { + return this.toString() + } + + get hexString(): string { + return arrayToHex(this.array) + } + + get blockNum(): UInt32 { + const bytes = this.array.slice(0, 4) + let num = 0 + for (let i = 0; i < 4; i++) { + num = (num << 8) + bytes[i] + } + return UInt32.from(num) + } +} diff --git a/src/chain/index.ts b/src/chain/index.ts index f22da7e..06b9691 100644 --- a/src/chain/index.ts +++ b/src/chain/index.ts @@ -20,3 +20,4 @@ export * from './permission-level' export * from './action' export * from './transaction' export * from './authority' +export * from './block-id' diff --git a/src/p2p/types.ts b/src/p2p/types.ts index 16ee25e..49982a7 100644 --- a/src/p2p/types.ts +++ b/src/p2p/types.ts @@ -1,4 +1,5 @@ import { + BlockId, Bytes, Checksum256, Int16, @@ -28,9 +29,9 @@ export class HandshakeMessage extends Struct { @Struct.field('signature') declare sig: Signature @Struct.field('string') declare p2pAddress: string @Struct.field('uint32') declare lastIrreversibleBlockNumber: UInt32 - @Struct.field('checksum256') declare lastIrreversibleBlockId: Checksum256 + @Struct.field(BlockId) declare lastIrreversibleBlockId: BlockId @Struct.field('uint32') declare headNum: UInt32 - @Struct.field('checksum256') declare headId: Checksum256 + @Struct.field(BlockId) declare headId: BlockId @Struct.field('string') declare os: string @Struct.field('string') declare agent: string @Struct.field('int16') declare generation: Int16 @@ -39,9 +40,9 @@ export class HandshakeMessage extends Struct { @Struct.type('chain_size_message') export class ChainSizeMessage extends Struct { @Struct.field('uint32') declare lastIrreversibleBlockNumber: UInt32 - @Struct.field('checksum256') declare lastIrreversibleBlockId: Checksum256 + @Struct.field(BlockId) declare lastIrreversibleBlockId: BlockId @Struct.field('uint32') declare headNum: UInt32 - @Struct.field('checksum256') declare headId: Checksum256 + @Struct.field(BlockId) declare headId: BlockId } @Struct.type('go_away_message') @@ -61,13 +62,13 @@ export class TimeMessage extends Struct { @Struct.type('notice_message') export class NoticeMessage extends Struct { @Struct.field('checksum256', {array: true}) declare knownTrx: Checksum256[] - @Struct.field('checksum256', {array: true}) declare knownBlocks: Checksum256[] + @Struct.field(BlockId, {array: true}) declare knownBlocks: BlockId[] } @Struct.type('request_message') export class RequestMessage extends Struct { @Struct.field('checksum256', {array: true}) declare reqTrx: Checksum256[] - @Struct.field('checksum256', {array: true}) declare reqBlocks: Checksum256[] + @Struct.field(BlockId, {array: true}) declare reqBlocks: BlockId[] } @Struct.type('sync_request_message') @@ -118,31 +119,20 @@ export class BlockHeader extends Struct { @Struct.field('uint32') declare timeSlot: UInt32 @Struct.field('name') declare producer: Name @Struct.field('uint16') declare confirmed: UInt16 - @Struct.field('checksum256') declare previous: Checksum256 - @Struct.field('checksum256') declare transaction_mroot: Checksum256 - @Struct.field('checksum256') declare action_mroot: Checksum256 + @Struct.field(BlockId) declare previous: BlockId + @Struct.field(BlockId) declare transaction_mroot: BlockId + @Struct.field(BlockId) declare action_mroot: BlockId @Struct.field('uint32') declare schedule_version: UInt32 @Struct.field(NewProducers, {optional: true}) new_producers?: NewProducers @Struct.field(HeaderExtension, {array: true}) declare header_extensions: HeaderExtension[] - get blockNum(): number { - const bytes = this.previous.array.slice(0, 4) - let num = 0 - for (let i = 0; i < 4; i++) { - num = (num << 8) + bytes[i] - } - return num + 1 + get blockNum(): UInt32 { + return this.previous.blockNum.adding(1) } - get id(): Checksum256 { + get id(): BlockId { const id = Checksum256.hash(Serializer.encode({object: this, type: BlockHeader})) - const numBuffer = new Uint8Array(4) - numBuffer[0] = (this.blockNum >> 24) & 0xff - numBuffer[1] = (this.blockNum >> 16) & 0xff - numBuffer[2] = (this.blockNum >> 8) & 0xff - numBuffer[3] = this.blockNum & 0xff - id.array.set(numBuffer, 0) - return id + return BlockId.fromBlockChecksum(id, this.blockNum) } } diff --git a/test/api.ts b/test/api.ts index 842eb92..4805ee3 100644 --- a/test/api.ts +++ b/test/api.ts @@ -10,6 +10,7 @@ import { APIClient, APIError, Asset, + BlockId, Checksum256, Float64, Name, @@ -139,6 +140,17 @@ suite('api v1', function () { ) }) + test('chain get_block (by id, typed)', async function () { + const block = await eos.v1.chain.get_block( + BlockId.from('00816d41e41f1462acb648b810b20f152d944fabd79aaff31c9f50102e4e5db9') + ) + assert.equal(Number(block.block_num), 8482113) + assert.equal( + block.id.hexString, + '00816d41e41f1462acb648b810b20f152d944fabd79aaff31c9f50102e4e5db9' + ) + }) + test('chain get_block (by num)', async function () { const block = await eos.v1.chain.get_block(8482113) assert.equal(Number(block.block_num), 8482113) diff --git a/test/chain.ts b/test/chain.ts index 0f99574..8e8144a 100644 --- a/test/chain.ts +++ b/test/chain.ts @@ -6,6 +6,7 @@ import { AnyTransaction, Asset, Authority, + BlockId, BlockTimestamp, Bytes, Checksum160, @@ -14,6 +15,7 @@ import { Int32, Int64, Name, + P2P, PermissionLevel, PublicKey, Signature, @@ -95,6 +97,24 @@ suite('chain', function () { }) }) + test('block id', function () { + const string = '048865fb643bca3b644647177f0cf363f7956794d0a7ec3bc6d29d93d9637308' + const blockId = BlockId.from(string) + assert.equal(String(blockId), string) + assert.equal(Number(blockId.blockNum), 76047867) + assert.equal(blockId.blockNum.equals(76047867), true) + assert.equal(blockId.blockNum.equals(UInt32.from(76047867)), true) + const blockId2 = BlockId.fromBlockChecksum( + '61375f2d5fbe6bbad86e424962a190e8309394b7bff4bf3e16b0a2a71e5a617c', + 7 + ) + assert.equal( + String(blockId2), + '000000075fbe6bbad86e424962a190e8309394b7bff4bf3e16b0a2a71e5a617c' + ) + assert.equal(blockId2.blockNum.equals(7), true) + }) + test('bytes', function () { assert.equal(Bytes.from('hello', 'utf8').toString('hex'), '68656c6c6f') assert.equal(Bytes.equal('beef', 'beef'), true) diff --git a/test/p2p.ts b/test/p2p.ts index c190c75..3bb88aa 100644 --- a/test/p2p.ts +++ b/test/p2p.ts @@ -24,7 +24,6 @@ suite('p2p', function () { const client = new P2PClient({...additionalOpts, provider: useProvider}) client.on('error', (e) => { - console.dir(e) assert.fail(e) })