Skip to content

Commit

Permalink
feat: adds getBlockProduction RPC call
Browse files Browse the repository at this point in the history
  • Loading branch information
stellaw1 authored and steveluscher committed Mar 27, 2022
1 parent 31b707b commit c08cfaf
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 0 deletions.
87 changes: 87 additions & 0 deletions web3.js/src/connection.ts
Expand Up @@ -754,6 +754,48 @@ export type BlockSignatures = {
blockTime: number | null;
};

/**
* recent block production information
*/
export type BlockProduction = Readonly<{
/** a dictionary of validator identities, as base-58 encoded strings. Value is a two element array containing the number of leader slots and the number of blocks produced */
byIdentity: Readonly<Record<string, ReadonlyArray<number>>>;
/** Block production slot range */
range: Readonly<{
/** first slot of the block production information (inclusive) */
firstSlot: number;
/** last slot of block production information (inclusive) */
lastSlot: number;
}>;
}>;

export type GetBlockProductionConfig = {
/** Optional commitment level */
commitment?: Commitment;
/** Slot range to return block production for. If parameter not provided, defaults to current epoch. */
range?: {
/** first slot to return block production information for (inclusive) */
firstSlot: number;
/** last slot to return block production information for (inclusive). If parameter not provided, defaults to the highest slot */
lastSlot?: number;
};
/** Only return results for this validator identity (base-58 encoded) */
identity?: string;
};

/**
* Expected JSON RPC response for the "getBlockProduction" message
*/
const BlockProductionResponseStruct = jsonRpcResultAndContext(
pick({
byIdentity: record(string(), array(number())),
range: pick({
firstSlot: number(),
lastSlot: number(),
}),
}),
);

/**
* A performance sample
*/
Expand Down Expand Up @@ -3230,6 +3272,51 @@ export class Connection {
};
}

/*
* Returns the current block height of the node
*/
async getBlockHeight(commitment?: Commitment): Promise<number> {
const args = this._buildArgs([], commitment);
const unsafeRes = await this._rpcRequest('getBlockHeight', args);
const res = create(unsafeRes, jsonRpcResult(number()));
if ('error' in res) {
throw new Error(
'failed to get block height information: ' + res.error.message,
);
}

return res.result;
}

/*
* Returns recent block production information from the current or previous epoch
*/
async getBlockProduction(
configOrCommitment?: GetBlockProductionConfig | Commitment,
): Promise<RpcResponseAndContext<BlockProduction>> {
let extra: Omit<GetBlockProductionConfig, 'commitment'> | undefined;
let commitment: Commitment | undefined;

if (typeof configOrCommitment === 'string') {
commitment = configOrCommitment;
} else if (configOrCommitment) {
const {commitment: c, ...rest} = configOrCommitment;
commitment = c;
extra = rest;
}

const args = this._buildArgs([], commitment, 'base64', extra);
const unsafeRes = await this._rpcRequest('getBlockProduction', args);
const res = create(unsafeRes, BlockProductionResponseStruct);
if ('error' in res) {
throw new Error(
'failed to get block production information: ' + res.error.message,
);
}

return res.result;
}

/**
* Fetch a confirmed or finalized transaction from the cluster.
*/
Expand Down
122 changes: 122 additions & 0 deletions web3.js/test/connection.test.ts
Expand Up @@ -1513,6 +1513,128 @@ describe('Connection', function () {
expect(result).to.be.empty;
});

it('get block height', async () => {
const commitment: Commitment = 'confirmed';

await mockRpcResponse({
method: 'getBlockHeight',
params: [{commitment: commitment}],
value: 10,
});

const blockHeight = await connection.getBlockHeight(commitment);
expect(blockHeight).to.be.a('number');
});

it('get block production', async () => {
const commitment: Commitment = 'processed';

// Find slot of the lowest confirmed block
await mockRpcResponse({
method: 'getFirstAvailableBlock',
params: [],
value: 1,
});
let firstSlot = await connection.getFirstAvailableBlock();

// Find current block height
await mockRpcResponse({
method: 'getBlockHeight',
params: [{commitment: commitment}],
value: 10,
});
let lastSlot = await connection.getBlockHeight(commitment);

const blockProductionConfig = {
commitment: commitment,
range: {
firstSlot,
lastSlot,
},
};

const blockProductionRet = {
byIdentity: {
'85iYT5RuzRTDgjyRa3cP8SYhM2j21fj7NhfJ3peu1DPr': [12, 10],
},
range: {
firstSlot,
lastSlot,
},
};

//mock RPC call with config specified
await mockRpcResponse({
method: 'getBlockProduction',
params: [blockProductionConfig],
value: blockProductionRet,
withContext: true,
});

//mock RPC call with commitment only
await mockRpcResponse({
method: 'getBlockProduction',
params: [{commitment: commitment}],
value: blockProductionRet,
withContext: true,
});

const result = await connection.getBlockProduction(blockProductionConfig);

if (!result) {
expect(result).to.be.ok;
return;
}

expect(result.context).to.be.ok;
expect(result.value).to.be.ok;

const resultContextSlot = result.context.slot;
expect(resultContextSlot).to.be.a('number');

const resultIdentityDictionary = result.value.byIdentity;
expect(resultIdentityDictionary).to.be.a('object');

for (var key in resultIdentityDictionary) {
expect(key).to.be.a('string');
expect(resultIdentityDictionary[key]).to.be.a('array');
expect(resultIdentityDictionary[key][0]).to.be.a('number');
expect(resultIdentityDictionary[key][1]).to.be.a('number');
}

const resultSlotRange = result.value.range;
expect(resultSlotRange.firstSlot).to.equal(firstSlot);
expect(resultSlotRange.lastSlot).to.equal(lastSlot);

const resultCommitmentOnly = await connection.getBlockProduction(
commitment,
);

if (!resultCommitmentOnly) {
expect(resultCommitmentOnly).to.be.ok;
return;
}
expect(resultCommitmentOnly.context).to.be.ok;
expect(resultCommitmentOnly.value).to.be.ok;

const resultCOContextSlot = result.context.slot;
expect(resultCOContextSlot).to.be.a('number');

const resultCOIdentityDictionary = result.value.byIdentity;
expect(resultCOIdentityDictionary).to.be.a('object');

for (var property in resultCOIdentityDictionary) {
expect(property).to.be.a('string');
expect(resultCOIdentityDictionary[property]).to.be.a('array');
expect(resultCOIdentityDictionary[property][0]).to.be.a('number');
expect(resultCOIdentityDictionary[property][1]).to.be.a('number');
}

const resultCOSlotRange = result.value.range;
expect(resultCOSlotRange.firstSlot).to.equal(firstSlot);
expect(resultCOSlotRange.lastSlot).to.equal(lastSlot);
});

it('get transaction', async () => {
await mockRpcResponse({
method: 'getSlot',
Expand Down

0 comments on commit c08cfaf

Please sign in to comment.