Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/lib/rpcClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,77 @@ describe('RPCClient', () => {
// Verify the response is defined
expect(result).toBeDefined()
})

describe('blockResults validator updates', () => {
const blockResults = (validatorUpdates: unknown) => ({
jsonrpc: '2.0',
id: 1,
result: {
height: '1',
txs_results: null,
finalize_block_events: [],
validator_updates: validatorUpdates,
consensus_param_updates: null,
},
})

it('decodes the cometbft v1 flattened pub_key shape', async () => {
nock(mockRpcUris[0])
.get('/block_results')
.query(true)
.reply(
200,
blockResults([
{
power: '3469906',
pub_key_bytes: 'fZqZqbnw0nQBHe76t9qIcvOyyHjoMH3upoHsUqRvcBU=',
pub_key_type: 'ed25519',
},
])
)

const client = new RPCClient(mockRpcUris[0])
const result = await client.blockResults(1)

expect(result.validatorUpdates).toHaveLength(1)
expect(result.validatorUpdates[0].pubkey.algorithm).toBe('ed25519')
expect(result.validatorUpdates[0].votingPower).toBe(3469906n)
})

it('still decodes the legacy nested pub_key shape', async () => {
nock(mockRpcUris[0])
.get('/block_results')
.query(true)
.reply(
200,
blockResults([
{
power: '100',
pub_key: {
type: 'tendermint/PubKeyEd25519',
value: 'fZqZqbnw0nQBHe76t9qIcvOyyHjoMH3upoHsUqRvcBU=',
},
},
])
)

const client = new RPCClient(mockRpcUris[0])
const result = await client.blockResults(1)

expect(result.validatorUpdates).toHaveLength(1)
expect(result.validatorUpdates[0].votingPower).toBe(100n)
})

it('handles blocks without validator updates', async () => {
nock(mockRpcUris[0])
.get('/block_results')
.query(true)
.reply(200, blockResults(null))

const client = new RPCClient(mockRpcUris[0])
const result = await client.blockResults(1)

expect(result.validatorUpdates).toHaveLength(0)
})
})
})
40 changes: 40 additions & 0 deletions src/lib/rpcClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,39 @@ function decodeBlockResults(
rpcBlockResult.result.end_block_events = end_block_events
}

rpcBlockResult.result.validator_updates =
rpcBlockResult.result.validator_updates?.map(normalizeValidatorUpdate) ??
null

return Responses.decodeBlockResults(rpcBlockResult)
}

const PUB_KEY_AMINO_TYPES: Record<string, string> = {
ed25519: 'tendermint/PubKeyEd25519',
secp256k1: 'tendermint/PubKeySecp256k1',
}

// cometbft v1 flattened `pub_key` into `pub_key_bytes`/`pub_key_type`, which
// the cosmjs decoder rejects. Blocks carrying a validator set change would
// otherwise stall the sync worker forever.
function normalizeValidatorUpdate(
update: RpcValidatorUpdate
): RpcValidatorUpdate {
if (update.pub_key !== undefined || update.pub_key_bytes === undefined) {
return update
}

const type = PUB_KEY_AMINO_TYPES[update.pub_key_type ?? '']
if (type === undefined) {
throw Error(`unknown pubkey type ${update.pub_key_type}`)
}

return {
power: update.power,
pub_key: { type, value: update.pub_key_bytes },
}
}

interface Header {
header: {
version: {
Expand Down Expand Up @@ -287,6 +317,16 @@ interface RpcBlockResultsResponse {
begin_block_events: RpcEvent[] | null
end_block_events: RpcEvent[] | null
finalize_block_events: RpcEvent[] | null
validator_updates: RpcValidatorUpdate[] | null
}

interface RpcValidatorUpdate {
power?: string
/** pre-cometbft-v1 shape */
pub_key?: { type: string; value: string }
/** cometbft v1 flattened shape */
pub_key_bytes?: string
pub_key_type?: string
}

interface RpcTxData {
Expand Down
Loading