diff --git a/web3.js/module.flow.js b/web3.js/module.flow.js index daba50f07f..1695fd590b 100644 --- a/web3.js/module.flow.js +++ b/web3.js/module.flow.js @@ -71,6 +71,13 @@ declare module '@solana/web3.js' { rpc: string | null, }; + declare export type ConfirmedBlock = { + blockhash: Blockhash, + previousBlockhash: Blockhash, + parentSlot: number, + transactions: Array<[Transaction, SignatureSuccess | TransactionError | null]>, + }; + declare export type KeyedAccountInfo = { accountId: PublicKey, accountInfo: AccountInfo, @@ -136,6 +143,7 @@ declare module '@solana/web3.js' { ): Promise>; getBalance(publicKey: PublicKey, commitment: ?Commitment): Promise; getClusterNodes(): Promise>; + getConfirmedBlock(): Promise; getVoteAccounts(commitment: ?Commitment): Promise; confirmTransactionAndContext( signature: TransactionSignature, diff --git a/web3.js/src/connection.js b/web3.js/src/connection.js index b477f79f7f..0caf172656 100644 --- a/web3.js/src/connection.js +++ b/web3.js/src/connection.js @@ -186,6 +186,22 @@ const Version = struct({ 'solana-core': 'string', }); +/** + * A ConfirmedBlock on the ledger + * + * @typedef {Object} ConfirmedBlock + * @property {Blockhash} blockhash Blockhash of this block + * @property {Blockhash} previousBlockhash Blockhash of this block's parent + * @property {number} parentSlot Slot index of this block's parent + * @property {Array>} transactions Vector of transactions paired with statuses + */ +type ConfirmedBlock = { + blockhash: Blockhash, + previousBlockhash: Blockhash, + parentSlot: number, + transactions: Array<[Transaction, GetSignatureStatusRpcResult]>, +}; + function createRpcRequest(url): RpcRequest { const server = jayson(async (request, callback) => { const options = { @@ -412,37 +428,48 @@ const GetMinimumBalanceForRentExemptionRpcResult = jsonRpcResult('number'); * Expected JSON RPC response for the "getConfirmedBlock" message */ export const GetConfirmedBlockRpcResult = jsonRpcResult( - struct.list([ - struct.tuple([ - 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', - }), + struct({ + blockhash: struct.list(['number']), + previousBlockhash: struct.list(['number']), + parentSlot: 'number', + transactions: struct.list([ + struct.tuple([ + struct({ + signatures: struct.list([struct.list(['number'])]), + message: struct({ + accountKeys: struct.list([struct.list(['number'])]), + header: struct({ + numRequiredSignatures: 'number', + numReadonlySignedAccounts: 'number', + numReadonlyUnsignedAccounts: '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']), + ]), + programIdIndex: 'number', + }), + ]), ]), - ]), - recent_blockhash: struct.list(['number']), + recentBlockhash: struct.list(['number']), + }), }), - }), - struct.union([struct({Ok: 'null'}), struct({Err: 'object'})]), + struct.union([ + 'null', + struct({ + status: GetSignatureStatusRpcResult, + fee: 'number', + }), + ]), + ]), ]), - ]), + }), ); /** @@ -1055,20 +1082,23 @@ export class Connection { * Fetch a list of Transactions and transaction statuses from the cluster * for a confirmed block */ - async getConfirmedBlock( - slot: number, - ): Promise< - Array<[Transaction, SignatureSuccess] | [Transaction, TransactionError]>, - > { + async getConfirmedBlock(slot: number): Promise { 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.fromRpcResult(result[0]), result[1]]; - }); + return { + blockhash: new PublicKey(result.result.blockhash).toString(), + previousBlockhash: new PublicKey( + result.result.previousBlockhash, + ).toString(), + parentSlot: result.result.parentSlot, + transactions: result.result.transactions.map(result => { + return [Transaction.fromRpcResult(result[0]), result[1]]; + }), + }; } /** diff --git a/web3.js/src/transaction.js b/web3.js/src/transaction.js index 345779151d..3bb62b496c 100644 --- a/web3.js/src/transaction.js +++ b/web3.js/src/transaction.js @@ -504,19 +504,19 @@ export class Transaction { */ static fromRpcResult(rpcResult: any): Transaction { const signatures = rpcResult.signatures.slice(1); - const accounts = rpcResult.message.account_keys.slice(1); + const accounts = rpcResult.message.accountKeys.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 recentBlockhash = rpcResult.message.recentBlockhash; const numRequiredSignatures = - rpcResult.message.header.num_required_signatures; + rpcResult.message.header.numRequiredSignatures; const numReadonlySignedAccounts = - rpcResult.message.header.num_readonly_signed_accounts; + rpcResult.message.header.numReadonlySignedAccounts; const numReadonlyUnsignedAccounts = - rpcResult.message.header.num_readonly_unsigned_accounts; + rpcResult.message.header.numReadonlyUnsignedAccounts; return Transaction._populate( signatures, accounts, diff --git a/web3.js/test/connection.test.js b/web3.js/test/connection.test.js index 775dcef2dc..94675852c4 100644 --- a/web3.js/test/connection.test.js +++ b/web3.js/test/connection.test.js @@ -426,12 +426,26 @@ test('get confirmed 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.getConfirmedBlock(0); - expect(zeroTransactions.length).toBe(0); + // Block 0 never has any transactions in automation localnet + const block0 = await connection.getConfirmedBlock(0); + const blockhash0 = block0.blockhash; + expect(block0.transactions.length).toBe(0); + expect(blockhash0).not.toBeNull(); + expect(block0.previousBlockhash).not.toBeNull(); + expect(block0.parentSlot).toBe(0); - const oneTransaction = await connection.getConfirmedBlock(1); - expect(oneTransaction.length).toBe(1); + // Find a block that has a transaction, usually Block 1 + let x = 1; + while (x < 10) { + const block1 = await connection.getConfirmedBlock(x); + if (block1.transactions.length >= 1) { + expect(block1.previousBlockhash).toBe(blockhash0); + expect(block1.blockhash).not.toBeNull(); + expect(block1.parentSlot).toBe(0); + break; + } + x++; + } }); test('get recent blockhash', async () => { diff --git a/web3.js/test/transaction.test.js b/web3.js/test/transaction.test.js index 6a40b54d7b..aa5894e501 100644 --- a/web3.js/test/transaction.test.js +++ b/web3.js/test/transaction.test.js @@ -105,7 +105,7 @@ test('transaction from rpc result', () => { const rawBlockhash = new PublicKey(0).toBuffer(); const rpcResult = { message: { - account_keys: [ + accountKeys: [ [5], new PublicKey(1).toBuffer(), new PublicKey(2).toBuffer(), @@ -114,19 +114,19 @@ test('transaction from rpc result', () => { new PublicKey(5).toBuffer(), ], header: { - num_readonly_signed_accounts: 0, - num_readonly_unsigned_accounts: 3, - num_required_signatures: 2, + num_ReadonlySignedAccounts: 0, + numReadonlyUnsignedAccounts: 3, + numRequiredSignatures: 2, }, instructions: [ [1], { accounts: [[3], 1, 2, 3], data: [[1], 0], - program_id_index: 4, + programIdIndex: 4, }, ], - recent_blockhash: rawBlockhash, + recentBlockhash: rawBlockhash, }, signatures: [[2], Array(64).fill(1), Array(64).fill(2)], };