Skip to content

Commit

Permalink
feat(namada): fetchBlockResults
Browse files Browse the repository at this point in the history
  • Loading branch information
egasimus committed Sep 26, 2024
1 parent 09047ad commit d1f130d
Show file tree
Hide file tree
Showing 4 changed files with 2,008 additions and 1,926 deletions.
209 changes: 120 additions & 89 deletions packages/namada/NamadaBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,102 +2,133 @@ import type * as Namada from './Namada'
import type { NamadaDecoder } from './NamadaDecode'
import { Decode } from './NamadaDecode'
import { Block, Transaction } from '@fadroma/cw'
import { Case } from '@hackbg/4mat'

type Connection = { url: string|URL, decode?: NamadaDecoder, chain?: Namada.Chain }

type NamadaBlockParameters =
ConstructorParameters<typeof Block>[0]
& Pick<NamadaBlock, 'chain'|'hash'|'header'|'transactions'|'responses'>

/** Responses from block API endpoints. */
export async function fetchBlockByHeight (
{ url, decode = Decode as unknown as NamadaDecoder, chain }: Connection,
{ height }: { height?: number|string|bigint, }
): Promise<NamadaBlock> {
if (!url) {
throw new Error("Can't fetch block: missing connection URL")
}
// Fetch block and results as undecoded JSON
const blockUrl = `${url}/block?height=${height??''}`
//const resultsUrl = `${url}/block_results?height=${height??''}`
const [block/*, results*/] = await Promise.all([
fetch(blockUrl).then(response=>response.text()),
//fetch(resultsUrl).then(response=>response.text()),
])
return blockFromResponses({
block: { url: blockUrl, response: block, },
//results: { url: resultsUrl, response: results, },
}, { chain, decode, height })
}

export async function fetchBlockResultsByHeight (
{ url }: Connection,
height?: bigint|number,
): Promise<BlockResults> {
const response = await fetch(`${url}/block_results?height=${height??''}`)
const { error, result } = await response.json() as {
error: {
data: string
},
result: {
height: string
txs_results: unknown[]|null
begin_block_events: unknown[]|null
end_block_events: unknown[]|null
validator_updated: unknown[]|null
consensus_param_updates: unknown[]|null
},
}
if (error) {
throw new Error(error.data)
}
const returned: Partial<BlockResults> = {}
for (const [key, value] of Object.entries(response)) {
returned[Case.camel(key) as keyof BlockResults] = value
}
return returned as BlockResults
}

type BlockResults = {
height: string
txsResults: TxResult[]|null
beginBlockEvents: unknown[]|null
endBlockEvents: EndBlockEvent[]|null
validatorUpdates: unknown[]|null
consensusParamUpdates: unknown[]|null
}

type TxResult = {
code: number
data: unknown|null
log: string
info: string
gas_wanted: string
gas_used: string
events: unknown[]
codespace: string
}

type EndBlockEvent = {
type: string
attributes: Array<{
key: string
value: string
index: boolean
}>
}

type DecodedResponses = {
hash: string,
header: NamadaBlock["header"]
transactions: Array<Partial<NamadaTransaction> & {id: string}>
}

export function blockFromResponses (
responses: NonNullable<NamadaBlock["responses"]>,
options: { decode?: NamadaDecoder, chain?: Namada.Chain, height?: string|number|bigint }
): NamadaBlock {
const decode = options.decode || Decode as unknown as NamadaDecoder
const { chain, height } = options
const blockResponse = responses.block.response
const { hash, header, transactions: decodedTransactions } = decode.block(
blockResponse, null /*responses.results?.response*/
) as DecodedResponses
const block = new NamadaBlock({
chain, hash, header, responses, transactions: [], height: height ? BigInt(height) : undefined
})
return Object.assign(block, { transactions: decodedTransactions.map(tx=>{
return new NamadaTransaction({ hash: tx?.id, ...tx, block })
}) })
}

class NamadaBlock extends Block {
constructor ({ responses, ...props }: NamadaBlockParameters) {
super(props)
this.#responses = responses
}
get chain (): Namada.Chain|undefined {
return super.chain as Namada.Chain|undefined
}
get chain (): Namada.Chain|undefined { return super.chain as Namada.Chain|undefined }
#responses?: {
block: { url: string, response: string }
results?: { url: string, response: string }
}
get responses () {
return this.#responses
}
get responses () { return this.#responses }
/** Block header. */
declare header: NamadaBlockHeader
/** Timestamp of block */
get time () {
return (this.header as any)?.time
}
get time () { return (this.header as any)?.time }
/** Transaction in block. */
declare transactions: NamadaTransaction[]

/** Responses from block API endpoints. */
static async fetchByHeight (
{ url, decode = Decode as unknown as NamadaDecoder, chain }: {
url: string|URL, decode?: NamadaDecoder, chain?: Namada.Chain
},
{ height, raw }: {
height?: number|string|bigint,
raw?: boolean,
}
): Promise<NamadaBlock> {
if (!url) {
throw new Error("Can't fetch block: missing connection URL")
}
// Fetch block and results as undecoded JSON
const blockUrl = `${url}/block?height=${height??''}`
//const resultsUrl = `${url}/block_results?height=${height??''}`
const [block/*, results*/] = await Promise.all([
fetch(blockUrl).then(response=>response.text()),
//fetch(resultsUrl).then(response=>response.text()),
])
return this.fromResponses({
block: { url: blockUrl, response: block, },
//results: { url: resultsUrl, response: results, },
}, { chain, decode, height })
}

static async fetchByHash (
_1: { url: string|URL, decode?: NamadaDecoder, chain?: Namada.Chain },
_2: { hash: string, raw?: boolean },
): Promise<NamadaBlock> {
throw new Error('NamadaBlock.fetchByHash: not implemented')
}

static fromResponses (
responses: NonNullable<NamadaBlock["responses"]>,
{ decode = Decode as unknown as NamadaDecoder, chain, height, raw = false }: {
decode?: NamadaDecoder
chain?: Namada.Chain,
height?: string|number|bigint,
raw?: boolean
},
): NamadaBlock {
const { hash, header, transactions } = decode.block(
responses.block.response,
null//responses.results?.response
) as {
hash: string,
header: NamadaBlock["header"]
transactions: Array<Partial<NamadaTransaction> & {id: string}>
}
const props = {
chain,
hash,
header,
responses,
transactions: [],
height: height ? BigInt(height) : undefined
}
const block = new NamadaBlock(props)
return Object.assign(block, {
transactions: transactions.map(tx=>new NamadaTransaction({
hash: tx?.id,
...tx,
block
}))
})
}
}

type NamadaBlockHeader = {
Expand Down Expand Up @@ -128,18 +159,18 @@ class NamadaTransaction extends Transaction {
return super.block as NamadaBlock|undefined
}
declare data: {
expiration?: string|null
timestamp?: string
feeToken?: string
expiration?: string|null
timestamp?: string
feeToken?: string
feeAmountPerGasUnit?: string
multiplier?: BigInt
gasLimitMultiplier?: BigInt
atomic?: boolean
txType?: 'Raw'|'Wrapper'|'Decrypted'|'Protocol'
sections?: object[]
content?: object
batch?: Array<{
hash: string,
multiplier?: bigint
gasLimitMultiplier?: bigint
atomic?: boolean
txType?: 'Raw'|'Wrapper'|'Decrypted'|'Protocol'
sections?: object[]
content?: object
batch?: Array<{
hash: string,
codeHash: string,
dataHash: string,
memoHash: string
Expand All @@ -148,6 +179,6 @@ class NamadaTransaction extends Transaction {
}

export {
NamadaBlock as default,
NamadaBlock as Block,
NamadaTransaction as Transaction,
}
24 changes: 12 additions & 12 deletions packages/namada/NamadaConnection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as CW from '@fadroma/cw'
import Block from './NamadaBlock'
import * as Block from './NamadaBlock'
import * as PoS from './NamadaPoS'
import * as PGF from './NamadaPGF'
import * as Gov from './NamadaGov'
Expand All @@ -16,23 +16,23 @@ export default class NamadaConnection extends CW.Connection {
}

override async fetchBlockImpl (
parameter?: ({ height: bigint|number }|{ hash: string }) & { raw?: boolean }
): Promise<Block> {
if (!this.url) {
throw new CW.Error("Can't fetch block: missing connection URL")
}
if (!parameter) {
parameter = {} as any
}
parameter?: { height: bigint|number }|{ hash: string }
): Promise<Block.Block> {
if (!this.url) throw new CW.Error("Can't fetch block: missing connection URL")
if (!parameter) parameter = {} as any
if ('height' in parameter!) {
return Block.fetchByHeight(this, parameter)
return Block.fetchBlockByHeight(this, parameter)
} else if ('hash' in parameter!) {
return Block.fetchByHash(this, parameter)
throw new Error('NamadaBlock.fetchByHash: not implemented')
} else {
return Block.fetchByHeight(this, {})
return Block.fetchBlockByHeight(this, {})
}
}

fetchBlockResultsImpl (parameter?: { height?: bigint|number }) {
return Block.fetchBlockResultsByHeight(this, parameter?.height)
}

override async fetchBalanceImpl (parameters: {
addresses: Record<string, string[]>,
parallel?: false
Expand Down
1 change: 1 addition & 0 deletions packages/namada/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@hackbg/fadroma": "workspace:*",
"@fadroma/cw": "workspace:*",
"@hackbg/cmds": "workspace:*",
"@hackbg/4mat": "workspace:*",
"@hackbg/borshest": "workspace:*"
},
"scripts": {
Expand Down
Loading

0 comments on commit d1f130d

Please sign in to comment.