Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

feat: Add getConfirmedBlock api #562

Merged
merged 1 commit into from
Nov 16, 2019
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
58 changes: 33 additions & 25 deletions src/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'})]),
]),
]),
Expand Down Expand Up @@ -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
*/
Expand All @@ -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]];
});
}

Expand Down
105 changes: 78 additions & 27 deletions src/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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<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();
for (let i = 0; i < signatureCount; i++) {
for (let i = 0; i < signatures.length; i++) {
const sigPubkeyPair = {
signature:
signatures[i].toString() == DEFAULT_SIGNATURE.toString()
Expand All @@ -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,
Expand Down
49 changes: 3 additions & 46 deletions test/connection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,61 +419,18 @@ 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;
}
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);
});

Expand Down