feat: add getConfirmedBlock api
This commit is contained in:
committed by
Michael Vines
parent
5212a98b83
commit
3c08e5b9c4
@ -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]];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user