diff --git a/web3.js/src/connection.js b/web3.js/src/connection.js index b4a379633a..b477f79f7f 100644 --- a/web3.js/src/connection.js +++ b/web3.js/src/connection.js @@ -409,17 +409,37 @@ const GetTotalSupplyRpcResult = jsonRpcResult('number'); const GetMinimumBalanceForRentExemptionRpcResult = jsonRpcResult('number'); /** - * Expected JSON RPC response for the "getBlocksSince" message + * Expected JSON RPC response for the "getConfirmedBlock" message */ -const GetBlocksSinceRpcResult = jsonRpcResult(struct.list(['number'])); - -/** - * Expected JSON RPC response for the "getBlock" message - */ -const GetBlockRpcResult = jsonRpcResult( +export const GetConfirmedBlockRpcResult = jsonRpcResult( struct.list([ struct.tuple([ - struct.list(['number']), + struct({ + signatures: struct.list([struct.list(['number'])]), + message: struct({ + account_keys: struct.list([struct.list(['number'])]), + header: struct({ + num_required_signatures: 'number', + num_readonly_signed_accounts: 'number', + num_readonly_unsigned_accounts: 'number', + }), + instructions: struct.list([ + struct.union([ + struct.list(['number']), + struct({ + accounts: struct.list([ + struct.union([struct.list(['number']), 'number']), + ]), + data: struct.list([ + struct.union([struct.list(['number']), 'number']), + ]), + program_id_index: 'number', + }), + ]), + ]), + recent_blockhash: struct.list(['number']), + }), + }), struct.union([struct({Ok: 'null'}), struct({Err: 'object'})]), ]), ]), @@ -1018,19 +1038,6 @@ export class Connection { }); } - /** - * Fetch a list of rooted blocks from the cluster - */ - async getBlocksSince(slot: number): Promise> { - const unsafeRes = await this._rpcRequest('getBlocksSince', [slot]); - const res = GetBlocksSinceRpcResult(unsafeRes); - if (res.error) { - throw new Error(res.error.message); - } - assert(typeof res.result !== 'undefined'); - return res.result; - } - /** * Fetch the node version */ @@ -1046,20 +1053,21 @@ export class Connection { /** * Fetch a list of Transactions and transaction statuses from the cluster + * for a confirmed block */ - async getBlock( + async getConfirmedBlock( slot: number, ): Promise< Array<[Transaction, SignatureSuccess] | [Transaction, TransactionError]>, > { - const unsafeRes = await this._rpcRequest('getBlock', [slot]); - const result = GetBlockRpcResult(unsafeRes); + const unsafeRes = await this._rpcRequest('getConfirmedBlock', [slot]); + const result = GetConfirmedBlockRpcResult(unsafeRes); if (result.error) { throw new Error(result.error.message); } assert(typeof result.result !== 'undefined'); return result.result.map(result => { - return [Transaction.from(result[0]), result[1]]; + return [Transaction.fromRpcResult(result[0]), result[1]]; }); } diff --git a/web3.js/src/transaction.js b/web3.js/src/transaction.js index 804dd47535..345779151d 100644 --- a/web3.js/src/transaction.js +++ b/web3.js/src/transaction.js @@ -32,6 +32,9 @@ const DEFAULT_SIGNATURE = Array(64).fill(0); */ export const PACKET_DATA_SIZE = 1280 - 40 - 8; +const PUBKEY_LENGTH = 32; +const SIGNATURE_LENGTH = 64; + /** * List of TransactionInstruction object fields that may be initialized at construction * @@ -442,25 +445,6 @@ export class Transaction { * Parse a wire transaction into a Transaction object. */ static from(buffer: Buffer): Transaction { - const PUBKEY_LENGTH = 32; - const SIGNATURE_LENGTH = 64; - - function isWritable( - i: number, - numRequiredSignatures: number, - numReadonlySignedAccounts: number, - numReadonlyUnsignedAccounts: number, - numKeys: number, - ): boolean { - return ( - i < numRequiredSignatures - numReadonlySignedAccounts || - (i >= numRequiredSignatures && - i < numKeys - numReadonlyUnsignedAccounts) - ); - } - - let transaction = new Transaction(); - // Slice up wire data let byteArray = [...buffer]; @@ -495,18 +479,85 @@ export class Transaction { for (let i = 0; i < instructionCount; i++) { let instruction = {}; instruction.programIndex = byteArray.shift(); - const accountIndexCount = shortvec.decodeLength(byteArray); - instruction.accountIndex = byteArray.slice(0, accountIndexCount); - byteArray = byteArray.slice(accountIndexCount); + const accountCount = shortvec.decodeLength(byteArray); + instruction.accounts = byteArray.slice(0, accountCount); + byteArray = byteArray.slice(accountCount); const dataLength = shortvec.decodeLength(byteArray); instruction.data = byteArray.slice(0, dataLength); byteArray = byteArray.slice(dataLength); instructions.push(instruction); } - // Populate Transaction object + return Transaction._populate( + signatures, + accounts, + instructions, + recentBlockhash, + numRequiredSignatures, + numReadonlySignedAccounts, + numReadonlyUnsignedAccounts, + ); + } + + /** + * Parse an RPC result into a Transaction object. + */ + static fromRpcResult(rpcResult: any): Transaction { + const signatures = rpcResult.signatures.slice(1); + const accounts = rpcResult.message.account_keys.slice(1); + const instructions = rpcResult.message.instructions.slice(1).map(ix => { + ix.accounts.shift(); + ix.data.shift(); + return ix; + }); + const recentBlockhash = rpcResult.message.recent_blockhash; + const numRequiredSignatures = + rpcResult.message.header.num_required_signatures; + const numReadonlySignedAccounts = + rpcResult.message.header.num_readonly_signed_accounts; + const numReadonlyUnsignedAccounts = + rpcResult.message.header.num_readonly_unsigned_accounts; + return Transaction._populate( + signatures, + accounts, + instructions, + recentBlockhash, + numRequiredSignatures, + numReadonlySignedAccounts, + numReadonlyUnsignedAccounts, + ); + } + + /** + * Populate Transaction object + * @private + */ + static _populate( + signatures: Array>, + accounts: Array>, + instructions: Array, + recentBlockhash: Array, + numRequiredSignatures: number, + numReadonlySignedAccounts: number, + numReadonlyUnsignedAccounts: number, + ): Transaction { + function isWritable( + i: number, + numRequiredSignatures: number, + numReadonlySignedAccounts: number, + numReadonlyUnsignedAccounts: number, + numKeys: number, + ): boolean { + return ( + i < numRequiredSignatures - numReadonlySignedAccounts || + (i >= numRequiredSignatures && + i < numKeys - numReadonlyUnsignedAccounts) + ); + } + + const transaction = new Transaction(); transaction.recentBlockhash = new PublicKey(recentBlockhash).toBase58(); - for (let i = 0; i < signatureCount; i++) { + for (let i = 0; i < signatures.length; i++) { const sigPubkeyPair = { signature: signatures[i].toString() == DEFAULT_SIGNATURE.toString() @@ -516,14 +567,14 @@ export class Transaction { }; transaction.signatures.push(sigPubkeyPair); } - for (let i = 0; i < instructionCount; i++) { + for (let i = 0; i < instructions.length; i++) { let instructionData = { keys: [], programId: new PublicKey(accounts[instructions[i].programIndex]), data: Buffer.from(instructions[i].data), }; - for (let j = 0; j < instructions[i].accountIndex.length; j++) { - const pubkey = new PublicKey(accounts[instructions[i].accountIndex[j]]); + for (let j = 0; j < instructions[i].accounts.length; j++) { + const pubkey = new PublicKey(accounts[instructions[i].accounts[j]]); instructionData.keys.push({ pubkey, diff --git a/web3.js/test/connection.test.js b/web3.js/test/connection.test.js index 99452bbda1..775dcef2dc 100644 --- a/web3.js/test/connection.test.js +++ b/web3.js/test/connection.test.js @@ -419,50 +419,7 @@ test('get minimum balance for rent exemption', async () => { expect(count).toBeGreaterThanOrEqual(0); }); -test('get blocks since slot', async () => { - const connection = new Connection(url); - - const expectedBlocks = [0, 1, 3, 4, 7, 8]; - mockRpc.push([ - url, - { - method: 'getBlocksSince', - params: [0], - }, - { - error: null, - result: expectedBlocks, - }, - ]); - - const blocks = await connection.getBlocksSince(0); - - if (mockRpcEnabled) { - expect(blocks.length).toEqual(6); - } else { - // No idea how many blocks since slot 0 on a live cluster - expect(blocks.length).toBeGreaterThan(0); - } - - const errorMessage = 'Slot 10000: SlotNotRooted'; - mockRpc.push([ - url, - { - method: 'getBlocksSince', - params: [10000], - }, - { - error: { - message: errorMessage, - }, - result: undefined, - }, - ]); - - await expect(connection.getBlocksSince(10000)).rejects.toThrow(errorMessage); -}); - -test('get block', async () => { +test('get confirmed block', async () => { if (mockRpcEnabled) { console.log('non-live test skipped'); return; @@ -470,10 +427,10 @@ test('get block', async () => { const connection = new Connection(url); // These test cases need to be updated when upstream solana RPC api is fleshed out - const zeroTransactions = await connection.getBlock(0); + const zeroTransactions = await connection.getConfirmedBlock(0); expect(zeroTransactions.length).toBe(0); - const oneTransaction = await connection.getBlock(1); + const oneTransaction = await connection.getConfirmedBlock(1); expect(oneTransaction.length).toBe(1); });