feat: add getConfirmedBlock api

This commit is contained in:
Justin Starry
2019-11-16 11:28:14 -05:00
committed by Michael Vines
parent 5212a98b83
commit 3c08e5b9c4
3 changed files with 114 additions and 98 deletions

View File

@ -409,17 +409,37 @@ const GetTotalSupplyRpcResult = jsonRpcResult('number');
const GetMinimumBalanceForRentExemptionRpcResult = 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'])); export const GetConfirmedBlockRpcResult = jsonRpcResult(
/**
* Expected JSON RPC response for the "getBlock" message
*/
const GetBlockRpcResult = jsonRpcResult(
struct.list([ struct.list([
struct.tuple([ 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.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'})]), 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<Array<number>> {
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 * Fetch the node version
*/ */
@ -1046,20 +1053,21 @@ export class Connection {
/** /**
* Fetch a list of Transactions and transaction statuses from the cluster * Fetch a list of Transactions and transaction statuses from the cluster
* for a confirmed block
*/ */
async getBlock( async getConfirmedBlock(
slot: number, slot: number,
): Promise< ): Promise<
Array<[Transaction, SignatureSuccess] | [Transaction, TransactionError]>, Array<[Transaction, SignatureSuccess] | [Transaction, TransactionError]>,
> { > {
const unsafeRes = await this._rpcRequest('getBlock', [slot]); const unsafeRes = await this._rpcRequest('getConfirmedBlock', [slot]);
const result = GetBlockRpcResult(unsafeRes); const result = GetConfirmedBlockRpcResult(unsafeRes);
if (result.error) { if (result.error) {
throw new Error(result.error.message); throw new Error(result.error.message);
} }
assert(typeof result.result !== 'undefined'); assert(typeof result.result !== 'undefined');
return result.result.map(result => { return result.result.map(result => {
return [Transaction.from(result[0]), result[1]]; return [Transaction.fromRpcResult(result[0]), result[1]];
}); });
} }

View File

@ -32,6 +32,9 @@ const DEFAULT_SIGNATURE = Array(64).fill(0);
*/ */
export const PACKET_DATA_SIZE = 1280 - 40 - 8; 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 * 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. * Parse a wire transaction into a Transaction object.
*/ */
static from(buffer: Buffer): Transaction { 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 // Slice up wire data
let byteArray = [...buffer]; let byteArray = [...buffer];
@ -495,18 +479,85 @@ export class Transaction {
for (let i = 0; i < instructionCount; i++) { for (let i = 0; i < instructionCount; i++) {
let instruction = {}; let instruction = {};
instruction.programIndex = byteArray.shift(); instruction.programIndex = byteArray.shift();
const accountIndexCount = shortvec.decodeLength(byteArray); const accountCount = shortvec.decodeLength(byteArray);
instruction.accountIndex = byteArray.slice(0, accountIndexCount); instruction.accounts = byteArray.slice(0, accountCount);
byteArray = byteArray.slice(accountIndexCount); byteArray = byteArray.slice(accountCount);
const dataLength = shortvec.decodeLength(byteArray); const dataLength = shortvec.decodeLength(byteArray);
instruction.data = byteArray.slice(0, dataLength); instruction.data = byteArray.slice(0, dataLength);
byteArray = byteArray.slice(dataLength); byteArray = byteArray.slice(dataLength);
instructions.push(instruction); 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<Array<number>>,
accounts: Array<Array<number>>,
instructions: Array<any>,
recentBlockhash: Array<number>,
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(); transaction.recentBlockhash = new PublicKey(recentBlockhash).toBase58();
for (let i = 0; i < signatureCount; i++) { for (let i = 0; i < signatures.length; i++) {
const sigPubkeyPair = { const sigPubkeyPair = {
signature: signature:
signatures[i].toString() == DEFAULT_SIGNATURE.toString() signatures[i].toString() == DEFAULT_SIGNATURE.toString()
@ -516,14 +567,14 @@ export class Transaction {
}; };
transaction.signatures.push(sigPubkeyPair); transaction.signatures.push(sigPubkeyPair);
} }
for (let i = 0; i < instructionCount; i++) { for (let i = 0; i < instructions.length; i++) {
let instructionData = { let instructionData = {
keys: [], keys: [],
programId: new PublicKey(accounts[instructions[i].programIndex]), programId: new PublicKey(accounts[instructions[i].programIndex]),
data: Buffer.from(instructions[i].data), data: Buffer.from(instructions[i].data),
}; };
for (let j = 0; j < instructions[i].accountIndex.length; j++) { for (let j = 0; j < instructions[i].accounts.length; j++) {
const pubkey = new PublicKey(accounts[instructions[i].accountIndex[j]]); const pubkey = new PublicKey(accounts[instructions[i].accounts[j]]);
instructionData.keys.push({ instructionData.keys.push({
pubkey, pubkey,

View File

@ -419,50 +419,7 @@ test('get minimum balance for rent exemption', async () => {
expect(count).toBeGreaterThanOrEqual(0); expect(count).toBeGreaterThanOrEqual(0);
}); });
test('get blocks since slot', async () => { test('get confirmed block', 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 () => {
if (mockRpcEnabled) { if (mockRpcEnabled) {
console.log('non-live test skipped'); console.log('non-live test skipped');
return; return;
@ -470,10 +427,10 @@ test('get block', async () => {
const connection = new Connection(url); const connection = new Connection(url);
// These test cases need to be updated when upstream solana RPC api is fleshed out // 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); expect(zeroTransactions.length).toBe(0);
const oneTransaction = await connection.getBlock(1); const oneTransaction = await connection.getConfirmedBlock(1);
expect(oneTransaction.length).toBe(1); expect(oneTransaction.length).toBe(1);
}); });