From 393c7653c74a7ead6288c17aa65f10811059ec20 Mon Sep 17 00:00:00 2001 From: Colin Ogoo Date: Tue, 30 Nov 2021 18:55:21 +0000 Subject: [PATCH] fix(web3.js): VoteAccount.fromAccountData() throws range error (#21091) * fix(vote-account): rangeError [ERR_OUT_OF_RANGE] error The web3 buffer layout is out-of-date with the current `VoteState` implementation. The buffer layout is updated to match the structure in https://github.com/solana-labs/solana/blob/master/account-decoder/src/parse_vote.rs fix #20786 * docs(vote account): update reference to match new payload * fix(vote-account): update buffer layout for prior voters Update buffer layout for prior voters to match serialized data * fix(vote-account): response showing buffers instead of public keys transform buffers into public keys * refactor(vote account): extract parsing into function calls * feat(vote account): address PR comments * fix(web3.js vote account): start prior voters array from given index * fix(web3.js vote account): incorrect data for prior voters array * Update web3.js/src/vote-account.ts Co-authored-by: Justin Starry Co-authored-by: Justin Starry --- .../clients/javascript-reference.md | 77 ++++++---- web3.js/src/vote-account.ts | 135 ++++++++++++++---- 2 files changed, 153 insertions(+), 59 deletions(-) diff --git a/docs/src/developing/clients/javascript-reference.md b/docs/src/developing/clients/javascript-reference.md index d1a026e47f..f378ddb7f6 100644 --- a/docs/src/developing/clients/javascript-reference.md +++ b/docs/src/developing/clients/javascript-reference.md @@ -578,34 +578,55 @@ let voteAccountFromData = web3.VoteAccount.fromAccountData(voteAccountInfo[0].ac console.log(voteAccountFromData); /* VoteAccount { - nodePubkey: PublicKey { - _bn: - }, - authorizedVoterPubkey: PublicKey { - _bn: - }, - authorizedWithdrawerPubkey: PublicKey { - _bn: - }, - commission: 0, - votes: [ - { slot: 124554051584, confirmationCount: 73536462 }, - { slot: 120259084288, confirmationCount: 73536463 }, - { slot: 115964116992, confirmationCount: 73536464 }, - { slot: 111669149696, confirmationCount: 73536465 }, - { slot: 107374182400, confirmationCount: 96542804 }, - { slot: 4294967296, confirmationCount: 1645464065 }, - { slot: 1099511627780, confirmationCount: 0 }, - { slot: 57088, confirmationCount: 3787757056 }, - { slot: 16516698632215534000, confirmationCount: 3236081224 }, - { slot: 328106138455040640, confirmationCount: 2194770418 }, - { slot: 290873038898, confirmationCount: 0 }, - ], - rootSlot: null, - epoch: 0, - credits: 0, - lastEpochCredits: 0, - epochCredits: [] + nodePubkey: PublicKey { + _bn: + }, + authorizedWithdrawer: PublicKey { + _bn: + }, + commission: 10, + rootSlot: 104570885, + votes: [ + { slot: 104570886, confirmationCount: 31 }, + { slot: 104570887, confirmationCount: 30 }, + { slot: 104570888, confirmationCount: 29 }, + { slot: 104570889, confirmationCount: 28 }, + { slot: 104570890, confirmationCount: 27 }, + { slot: 104570891, confirmationCount: 26 }, + { slot: 104570892, confirmationCount: 25 }, + { slot: 104570893, confirmationCount: 24 }, + { slot: 104570894, confirmationCount: 23 }, + ... + ], + authorizedVoters: [ { epoch: 242, authorizedVoter: [PublicKey] } ], + priorVoters: [ + [Object], [Object], [Object], + [Object], [Object], [Object], + [Object], [Object], [Object], + [Object], [Object], [Object], + [Object], [Object], [Object], + [Object], [Object], [Object], + [Object], [Object], [Object], + [Object], [Object], [Object], + [Object], [Object], [Object], + [Object], [Object], [Object], + [Object], [Object] + ], + epochCredits: [ + { epoch: 179, credits: 33723163, prevCredits: 33431259 }, + { epoch: 180, credits: 34022643, prevCredits: 33723163 }, + { epoch: 181, credits: 34331103, prevCredits: 34022643 }, + { epoch: 182, credits: 34619348, prevCredits: 34331103 }, + { epoch: 183, credits: 34880375, prevCredits: 34619348 }, + { epoch: 184, credits: 35074055, prevCredits: 34880375 }, + { epoch: 185, credits: 35254965, prevCredits: 35074055 }, + { epoch: 186, credits: 35437863, prevCredits: 35254965 }, + { epoch: 187, credits: 35672671, prevCredits: 35437863 }, + { epoch: 188, credits: 35950286, prevCredits: 35672671 }, + { epoch: 189, credits: 36228439, prevCredits: 35950286 }, + ... + ], + lastTimestamp: { slot: 104570916, timestamp: 1635730116 } } */ ``` diff --git a/web3.js/src/vote-account.ts b/web3.js/src/vote-account.ts index e3d1d93997..5e592ca2c0 100644 --- a/web3.js/src/vote-account.ts +++ b/web3.js/src/vote-account.ts @@ -23,6 +23,22 @@ export type EpochCredits = { prevCredits: number; }; +export type AuthorizedVoter = { + epoch: number; + authorizedVoter: PublicKey; +}; + +export type PriorVoter = { + authorizedPubkey: PublicKey; + epochOfLastAuthorizedSwitch: number; + targetEpoch: number; +}; + +export type BlockTimestamp = { + slot: number; + timetamp: number; +}; + /** * See https://github.com/solana-labs/solana/blob/8a12ed029cfa38d4a45400916c2463fb82bbec8c/programs/vote_api/src/vote_state.rs#L68-L88 * @@ -30,8 +46,7 @@ export type EpochCredits = { */ const VoteAccountLayout = BufferLayout.struct([ Layout.publicKey('nodePubkey'), - Layout.publicKey('authorizedVoterPubkey'), - Layout.publicKey('authorizedWithdrawerPubkey'), + Layout.publicKey('authorizedWithdrawer'), BufferLayout.u8('commission'), BufferLayout.nu64(), // votes.length BufferLayout.seq( @@ -44,9 +59,31 @@ const VoteAccountLayout = BufferLayout.struct([ ), BufferLayout.u8('rootSlotValid'), BufferLayout.nu64('rootSlot'), - BufferLayout.nu64('epoch'), - BufferLayout.nu64('credits'), - BufferLayout.nu64('lastEpochCredits'), + BufferLayout.nu64(), // authorizedVoters.length + BufferLayout.seq( + BufferLayout.struct([ + BufferLayout.nu64('epoch'), + Layout.publicKey('authorizedVoter'), + ]), + BufferLayout.offset(BufferLayout.u32(), -8), + 'authorizedVoters', + ), + BufferLayout.struct( + [ + BufferLayout.seq( + BufferLayout.struct([ + Layout.publicKey('authorizedPubkey'), + BufferLayout.nu64('epochOfLastAuthorizedSwitch'), + BufferLayout.nu64('targetEpoch'), + ]), + 32, + 'buf', + ), + BufferLayout.nu64('idx'), + BufferLayout.u8('isEmpty'), + ], + 'priorVoters', + ), BufferLayout.nu64(), // epochCredits.length BufferLayout.seq( BufferLayout.struct([ @@ -57,19 +94,22 @@ const VoteAccountLayout = BufferLayout.struct([ BufferLayout.offset(BufferLayout.u32(), -8), 'epochCredits', ), + BufferLayout.struct( + [BufferLayout.nu64('slot'), BufferLayout.nu64('timestamp')], + 'lastTimestamp', + ), ]); type VoteAccountArgs = { nodePubkey: PublicKey; - authorizedVoterPubkey: PublicKey; - authorizedWithdrawerPubkey: PublicKey; + authorizedWithdrawer: PublicKey; commission: number; - votes: Array; rootSlot: number | null; - epoch: number; - credits: number; - lastEpochCredits: number; - epochCredits: Array; + votes: Lockout[]; + authorizedVoters: AuthorizedVoter[]; + priorVoters: PriorVoter[]; + epochCredits: EpochCredits[]; + lastTimestamp: BlockTimestamp; }; /** @@ -77,30 +117,28 @@ type VoteAccountArgs = { */ export class VoteAccount { nodePubkey: PublicKey; - authorizedVoterPubkey: PublicKey; - authorizedWithdrawerPubkey: PublicKey; + authorizedWithdrawer: PublicKey; commission: number; - votes: Array; rootSlot: number | null; - epoch: number; - credits: number; - lastEpochCredits: number; - epochCredits: Array; + votes: Lockout[]; + authorizedVoters: AuthorizedVoter[]; + priorVoters: PriorVoter[]; + epochCredits: EpochCredits[]; + lastTimestamp: BlockTimestamp; /** * @internal */ constructor(args: VoteAccountArgs) { this.nodePubkey = args.nodePubkey; - this.authorizedVoterPubkey = args.authorizedVoterPubkey; - this.authorizedWithdrawerPubkey = args.authorizedWithdrawerPubkey; + this.authorizedWithdrawer = args.authorizedWithdrawer; this.commission = args.commission; - this.votes = args.votes; this.rootSlot = args.rootSlot; - this.epoch = args.epoch; - this.credits = args.credits; - this.lastEpochCredits = args.lastEpochCredits; + this.votes = args.votes; + this.authorizedVoters = args.authorizedVoters; + this.priorVoters = args.priorVoters; this.epochCredits = args.epochCredits; + this.lastTimestamp = args.lastTimestamp; } /** @@ -112,7 +150,8 @@ export class VoteAccount { static fromAccountData( buffer: Buffer | Uint8Array | Array, ): VoteAccount { - const va = VoteAccountLayout.decode(toBuffer(buffer), 0); + const versionOffset = 4; + const va = VoteAccountLayout.decode(toBuffer(buffer), versionOffset); let rootSlot: number | null = va.rootSlot; if (!va.rootSlotValid) { @@ -121,15 +160,49 @@ export class VoteAccount { return new VoteAccount({ nodePubkey: new PublicKey(va.nodePubkey), - authorizedVoterPubkey: new PublicKey(va.authorizedVoterPubkey), - authorizedWithdrawerPubkey: new PublicKey(va.authorizedWithdrawerPubkey), + authorizedWithdrawer: new PublicKey(va.authorizedWithdrawer), commission: va.commission, votes: va.votes, rootSlot, - epoch: va.epoch, - credits: va.credits, - lastEpochCredits: va.lastEpochCredits, + authorizedVoters: va.authorizedVoters.map(parseAuthorizedVoter), + priorVoters: getPriorVoters(va.priorVoters), epochCredits: va.epochCredits, + lastTimestamp: va.lastTimestamp, }); } } + +function parseAuthorizedVoter({epoch, authorizedVoter}: AuthorizedVoter) { + return { + epoch, + authorizedVoter: new PublicKey(authorizedVoter), + }; +} + +function parsePriorVoters({ + authorizedPubkey, + epochOfLastAuthorizedSwitch, + targetEpoch, +}: PriorVoter) { + return { + authorizedPubkey: new PublicKey(authorizedPubkey), + epochOfLastAuthorizedSwitch, + targetEpoch, + }; +} + +function getPriorVoters({ + buf, + idx, + isEmpty, +}: { + buf: PriorVoter[]; + idx: number; + isEmpty: boolean; +}): PriorVoter[] { + if (isEmpty) { + return []; + } + + return [...buf.slice(idx + 1).map(parsePriorVoters), ...buf.slice(0, idx)]; +}