diff --git a/web3.js/src/connection.ts b/web3.js/src/connection.ts index 216d1fd090..1048cfa0ed 100644 --- a/web3.js/src/connection.ts +++ b/web3.js/src/connection.ts @@ -583,6 +583,22 @@ export type ConfirmedBlock = { blockTime: number | null; }; +/** + * A ConfirmedBlock on the ledger with signatures only + */ +export type ConfirmedBlockSignatures = { + /** Blockhash of this block */ + blockhash: Blockhash; + /** Blockhash of this block's parent */ + previousBlockhash: Blockhash; + /** Slot index of this block's parent */ + parentSlot: number; + /** Vector of signatures */ + signatures: Array; + /** The unix timestamp of when the block was processed */ + blockTime: number | null; +}; + /** * A performance sample */ @@ -915,13 +931,6 @@ const StakeActivationResult = pick({ inactive: number(), }); -/** - * Expected JSON RPC response for the "getConfirmedSignaturesForAddress" message - */ -const GetConfirmedSignaturesForAddressRpcResult = jsonRpcResult( - array(string()), -); - /** * Expected JSON RPC response for the "getConfirmedSignaturesForAddress2" message */ @@ -1231,6 +1240,21 @@ const GetConfirmedBlockRpcResult = jsonRpcResult( ), ); +/** + * Expected JSON RPC response for the "getConfirmedBlockSignatures" message + */ +const GetConfirmedBlockSignaturesRpcResult = jsonRpcResult( + nullable( + pick({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + signatures: array(string()), + blockTime: nullable(number()), + }), + ), +); + /** * Expected JSON RPC response for the "getConfirmedTransaction" message */ @@ -2284,15 +2308,16 @@ export class Connection { /** * Fetch the current total currency supply of the cluster in lamports + * @deprecated Deprecated since v1.2.8. Use `Connection.getSupply()` instead. */ async getTotalSupply(commitment?: Commitment): Promise { const args = this._buildArgs([], commitment); - const unsafeRes = await this._rpcRequest('getTotalSupply', args); - const res = create(unsafeRes, jsonRpcResult(number())); + const unsafeRes = await this._rpcRequest('getSupply', args); + const res = create(unsafeRes, GetSupplyRpcResult); if ('error' in res) { throw new Error('failed to get total supply: ' + res.error.message); } - return res.result; + return res.result.value.total; } /** @@ -2477,6 +2502,27 @@ export class Connection { return result; } + /** + * Fetch a list of Signatures from the cluster for a confirmed block, excluding rewards + */ + async getConfirmedBlockSignatures( + slot: number, + ): Promise { + const unsafeRes = await this._rpcRequest('getConfirmedBlock', [ + slot, + {transactionDetails: 'signatures', rewards: false}, + ]); + const res = create(unsafeRes, GetConfirmedBlockSignaturesRpcResult); + if ('error' in res) { + throw new Error('failed to get confirmed block: ' + res.error.message); + } + const result = res.result; + if (!result) { + throw new Error('Confirmed block ' + slot + ' not found'); + } + return result; + } + /** * Fetch a transaction details for a confirmed transaction */ @@ -2544,6 +2590,7 @@ export class Connection { /** * Fetch a list of all the confirmed signatures for transactions involving an address * within a specified slot range. Max range allowed is 10,000 slots. + * @deprecated Deprecated since v1.3. Use `Connection.getConfirmedSignaturesForAddress2()` instead. * * @param address queried address * @param startSlot start slot, inclusive @@ -2554,17 +2601,59 @@ export class Connection { startSlot: number, endSlot: number, ): Promise> { - const unsafeRes = await this._rpcRequest( - 'getConfirmedSignaturesForAddress', - [address.toBase58(), startSlot, endSlot], - ); - const res = create(unsafeRes, GetConfirmedSignaturesForAddressRpcResult); - if ('error' in res) { - throw new Error( - 'failed to get confirmed signatures for address: ' + res.error.message, - ); + let options: any = {}; + + let firstAvailableBlock = await this.getFirstAvailableBlock(); + while (!('until' in options)) { + startSlot--; + if (startSlot <= 0 || startSlot < firstAvailableBlock) { + break; + } + + try { + const block = await this.getConfirmedBlockSignatures(startSlot); + if (block.signatures.length > 0) { + options.until = block.signatures[ + block.signatures.length - 1 + ].toString(); + } + } catch (err) { + if (err.message.includes('skipped')) { + continue; + } else { + throw err; + } + } } - return res.result; + + let highestConfirmedRoot = await this.getSlot('finalized'); + while (!('before' in options)) { + endSlot++; + if (endSlot > highestConfirmedRoot) { + break; + } + + try { + const block = await this.getConfirmedBlockSignatures(endSlot); + if (block.signatures.length > 0) { + options.before = block.signatures[ + block.signatures.length - 1 + ].toString(); + } + } catch (err) { + if (err.message.includes('skipped')) { + continue; + } else { + throw err; + } + } + } + + const confirmedSignatureInfo = await this.getConfirmedSignaturesForAddress2( + address, + options, + ); + return confirmedSignatureInfo.map(info => info.signature); } /** diff --git a/web3.js/test/connection.test.ts b/web3.js/test/connection.test.ts index de96d3b85e..2a335e640e 100644 --- a/web3.js/test/connection.test.ts +++ b/web3.js/test/connection.test.ts @@ -492,9 +492,15 @@ describe('Connection', () => { it('get total supply', async () => { await mockRpcResponse({ - method: 'getTotalSupply', + method: 'getSupply', params: [], - value: 1000000, + value: { + total: 1000000, + circulating: 100000, + nonCirculating: 900000, + nonCirculatingAccounts: [new Account().publicKey.toBase58()], + }, + withContext: true, }); const count = await connection.getTotalSupply(); @@ -597,9 +603,50 @@ describe('Connection', () => { // getConfirmedSignaturesForAddress tests... await mockRpcResponse({ - method: 'getConfirmedSignaturesForAddress', - params: [address.toBase58(), slot, slot + 1], - value: [expectedSignature], + method: 'getFirstAvailableBlock', + params: [], + value: 0, + }); + const mockSignature = + '5SHZ9NwpnS9zYnauN7pnuborKf39zGMr11XpMC59VvRSeDJNcnYLecmdxXCVuBFPNQLdCBBjyZiNCL4KoHKr3tvz'; + await mockRpcResponse({ + method: 'getConfirmedBlock', + params: [slot, {transactionDetails: 'signatures', rewards: false}], + value: { + blockTime: 1614281964, + blockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', + previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', + parentSlot: 1, + signatures: [mockSignature], + }, + }); + await mockRpcResponse({ + method: 'getSlot', + params: [], + value: 123, + }); + await mockRpcResponse({ + method: 'getConfirmedBlock', + params: [slot + 2, {transactionDetails: 'signatures', rewards: false}], + value: { + blockTime: 1614281964, + blockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', + previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', + parentSlot: 1, + signatures: [mockSignature], + }, + }); + await mockRpcResponse({ + method: 'getConfirmedSignaturesForAddress2', + params: [address.toBase58(), {before: mockSignature}], + value: [ + { + signature: expectedSignature, + slot, + err: null, + memo: null, + }, + ], }); const confirmedSignatures = await connection.getConfirmedSignaturesForAddress( @@ -611,17 +658,17 @@ describe('Connection', () => { const badSlot = Number.MAX_SAFE_INTEGER - 1; await mockRpcResponse({ - method: 'getConfirmedSignaturesForAddress', - params: [address.toBase58(), badSlot, badSlot + 1], - value: [], + method: 'getConfirmedBlock', + params: [badSlot - 1, {transactionDetails: 'signatures', rewards: false}], + error: {message: 'Block not available for slot ' + badSlot}, }); - - const emptySignatures = await connection.getConfirmedSignaturesForAddress( - address, - badSlot, - badSlot + 1, - ); - expect(emptySignatures).to.have.length(0); + expect( + connection.getConfirmedSignaturesForAddress( + address, + badSlot, + badSlot + 1, + ), + ).to.be.rejected; // getConfirmedSignaturesForAddress2 tests... await mockRpcResponse({ @@ -1288,6 +1335,94 @@ describe('Connection', () => { ); }); + it('get confirmed block signatures', async () => { + await mockRpcResponse({ + method: 'getSlot', + params: [], + value: 1, + }); + + while ((await connection.getSlot()) <= 0) { + continue; + } + + await mockRpcResponse({ + method: 'getConfirmedBlock', + params: [ + 0, + { + transactionDetails: 'signatures', + rewards: false, + }, + ], + value: { + blockTime: 1614281964, + blockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', + previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', + parentSlot: 0, + signatures: [], + }, + }); + + // Block 0 never has any transactions in test validator + const block0 = await connection.getConfirmedBlockSignatures(0); + const blockhash0 = block0.blockhash; + expect(block0.signatures).to.have.length(0); + expect(blockhash0).not.to.be.null; + expect(block0.previousBlockhash).not.to.be.null; + expect(block0.parentSlot).to.eq(0); + expect(block0).to.not.have.property('rewards'); + + await mockRpcResponse({ + method: 'getConfirmedBlock', + params: [ + 1, + { + transactionDetails: 'signatures', + rewards: false, + }, + ], + value: { + blockTime: 1614281964, + blockhash: '57zQNBZBEiHsCZFqsaY6h176ioXy5MsSLmcvHkEyaLGy', + previousBlockhash: 'H5nJ91eGag3B5ZSRHZ7zG5ZwXJ6ywCt2hyR8xCsV7xMo', + parentSlot: 0, + signatures: [ + 'w2Zeq8YkpyB463DttvfzARD7k9ZxGEwbsEw4boEK7jDp3pfoxZbTdLFSsEPhzXhpCcjGi2kHtHFobgX49MMhbWt', + '4oCEqwGrMdBeMxpzuWiukCYqSfV4DsSKXSiVVCh1iJ6pS772X7y219JZP3mgqBz5PhsvprpKyhzChjYc3VSBQXzG', + ], + }, + }); + + // Find a block that has a transaction, usually Block 1 + let x = 1; + while (x < 10) { + const block1 = await connection.getConfirmedBlockSignatures(x); + if (block1.signatures.length >= 1) { + expect(block1.previousBlockhash).to.eq(blockhash0); + expect(block1.blockhash).not.to.be.null; + expect(block1.parentSlot).to.eq(0); + expect(block1.signatures[0]).not.to.be.null; + expect(block1).to.not.have.property('rewards'); + break; + } + x++; + } + + await mockRpcResponse({ + method: 'getConfirmedBlock', + params: [Number.MAX_SAFE_INTEGER], + error: { + message: `Block not available for slot ${Number.MAX_SAFE_INTEGER}`, + }, + }); + await expect( + connection.getConfirmedBlockSignatures(Number.MAX_SAFE_INTEGER), + ).to.be.rejectedWith( + `Block not available for slot ${Number.MAX_SAFE_INTEGER}`, + ); + }); + it('get recent blockhash', async () => { const commitments: Commitment[] = ['processed', 'confirmed', 'finalized']; for (const commitment of commitments) {