Skip to content

Commit

Permalink
BlockId implementation (#66)
Browse files Browse the repository at this point in the history
* Added BlockId type

* Updating type definitions for BlockId

All instances of Checksum256 that reference a BlockId have been updated to use the new BlockId type.

* Test coverage for BlockId

* Removed placeholder math

* Moving `BlockId` to its own serializable class

* Added missing fromABI call

* Removed BlockId from builtins
  • Loading branch information
aaroncox authored May 17, 2023
1 parent 1de041c commit 18e9390
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 37 deletions.
5 changes: 3 additions & 2 deletions src/api/v1/chain.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {APIClient} from '../client'

import {
BlockIdType,
Bytes,
Checksum160,
Checksum256,
Expand Down Expand Up @@ -81,15 +82,15 @@ 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},
responseType: GetBlockResponse,
})
}

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},
Expand Down
21 changes: 11 additions & 10 deletions src/api/v1/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AnyAction,
Asset,
Authority,
BlockId,
BlockTimestamp,
Bytes,
Checksum160,
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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). */
Expand All @@ -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(
Expand Down Expand Up @@ -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
}

Expand Down
78 changes: 78 additions & 0 deletions src/chain/block-id.ts
Original file line number Diff line number Diff line change
@@ -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)
}
}
1 change: 1 addition & 0 deletions src/chain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export * from './permission-level'
export * from './action'
export * from './transaction'
export * from './authority'
export * from './block-id'
38 changes: 14 additions & 24 deletions src/p2p/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
BlockId,
Bytes,
Checksum256,
Int16,
Expand Down Expand Up @@ -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
Expand All @@ -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')
Expand All @@ -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')
Expand Down Expand Up @@ -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)
}
}

Expand Down
12 changes: 12 additions & 0 deletions test/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
APIClient,
APIError,
Asset,
BlockId,
Checksum256,
Float64,
Name,
Expand Down Expand Up @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions test/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
AnyTransaction,
Asset,
Authority,
BlockId,
BlockTimestamp,
Bytes,
Checksum160,
Expand All @@ -14,6 +15,7 @@ import {
Int32,
Int64,
Name,
P2P,
PermissionLevel,
PublicKey,
Signature,
Expand Down Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion test/p2p.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ suite('p2p', function () {
const client = new P2PClient({...additionalOpts, provider: useProvider})

client.on('error', (e) => {
console.dir(e)
assert.fail(e)
})

Expand Down

0 comments on commit 18e9390

Please sign in to comment.