diff --git a/web3.js/module.flow.js b/web3.js/module.flow.js index 9a300d5dee..3274938d64 100644 --- a/web3.js/module.flow.js +++ b/web3.js/module.flow.js @@ -32,6 +32,13 @@ declare module '@solana/web3.js' { secretKey: Buffer; } + // === src/fee-calculator.js === + declare export type FeeCalculator = { + lamportsPerSignature: number, + targetSignaturesPerSlot: number, + targetLamportsPerSignature: number, + }; + // === src/budget-program.js === /* TODO */ @@ -55,6 +62,13 @@ declare module '@solana/web3.js' { accountInfo: AccountInfo, }; + declare export type VoteAccountInfo = { + votePubkey: string, + nodePubkey: string, + stake: number, + commission: number, + }; + declare type AccountChangeCallback = (accountInfo: AccountInfo) => void; declare type ProgramAccountChangeCallback = ( keyedAccountInfo: KeyedAccountInfo, @@ -72,13 +86,14 @@ declare module '@solana/web3.js' { getAccountInfo(publicKey: PublicKey): Promise; getBalance(publicKey: PublicKey): Promise; getClusterNodes(): Promise>; + getEpochVoteAccounts(): Promise>; confirmTransaction(signature: TransactionSignature): Promise; getSlotLeader(): Promise; getSignatureStatus( signature: TransactionSignature, ): Promise; getTransactionCount(): Promise; - getRecentBlockhash(): Promise; + getRecentBlockhash(): Promise<[Blockhash, FeeCalculator]>; requestAirdrop( to: PublicKey, amount: number, diff --git a/web3.js/src/connection.js b/web3.js/src/connection.js index 0616d5ba6d..1a9ba4b40c 100644 --- a/web3.js/src/connection.js +++ b/web3.js/src/connection.js @@ -12,6 +12,7 @@ import {PublicKey} from './publickey'; import {Transaction} from './transaction'; import {sleep} from './util/sleep'; import type {Blockhash} from './blockhash'; +import type {FeeCalculator} from './fee-calculator'; import type {Account} from './account'; import type {TransactionSignature} from './transaction'; @@ -203,8 +204,23 @@ const GetTransactionCountRpcResult = jsonRpcResult('number'); /** * Expected JSON RPC response for the "getRecentBlockhash" message */ -const GetRecentBlockhash = jsonRpcResult(['string', 'object']); -const GetRecentBlockhash_014 = jsonRpcResult('string'); // Legacy v0.14 response. TODO: Remove in July 2019 +const GetRecentBlockhash = jsonRpcResult([ + 'string', + struct({ + lamportsPerSignature: 'number', + targetLamportsPerSignature: 'number', + targetSignaturesPerSlot: 'number', + }), +]); +/** + * @ignore + */ +const GetRecentBlockhash_015 = jsonRpcResult([ + 'string', + struct({ + lamportsPerSignature: 'number', + }), +]); /** * Expected JSON RPC response for the "requestAirdrop" message @@ -292,6 +308,12 @@ export type TransactionError = {| Err: Object, |}; + +/** + * @ignore + */ +type BlockhashAndFeeCalculator = [Blockhash, FeeCalculator]; // This type exists to workaround an esdoc parse error + /** * A connection to a fullnode JSON RPC endpoint */ @@ -473,28 +495,32 @@ export class Connection { /** * Fetch a recent blockhash from the cluster */ - async getRecentBlockhash(): Promise { + async getRecentBlockhash(): Promise { const unsafeRes = await this._rpcRequest('getRecentBlockhash', []); - // Legacy v0.14 response. TODO: Remove in July 2019 + // Legacy v0.15 response. TODO: Remove in August 2019 try { - const res_014 = GetRecentBlockhash_014(unsafeRes); - if (res_014.error) { - throw new Error(res_014.error.message); + const res_015 = GetRecentBlockhash_015(unsafeRes); + if (res_015.error) { + throw new Error(res_015.error.message); } - return res_014.result; + const [blockhash, feeCalculator] = res_015.result; + feeCalculator.targetSignaturesPerSlot = 42; + feeCalculator.targetLamportsPerSignature = + feeCalculator.lamportsPerSignature; + + return [blockhash, feeCalculator]; } catch (e) { // Not legacy format } - // End Legacy v0.14 response + // End Legacy v0.15 response const res = GetRecentBlockhash(unsafeRes); if (res.error) { throw new Error(res.error.message); } assert(typeof res.result !== 'undefined'); - // TODO: deserialize and expose FeeCalculator in res.result[1] - return res.result[0]; + return res.result; } /** @@ -552,7 +578,10 @@ export class Connection { let attempts = 0; const startTime = Date.now(); for (;;) { - const recentBlockhash = await this.getRecentBlockhash(); + const [ + recentBlockhash, + //feeCalculator, + ] = await this.getRecentBlockhash(); if (this._blockhashInfo.recentBlockhash != recentBlockhash) { this._blockhashInfo = { diff --git a/web3.js/src/fee-calculator.js b/web3.js/src/fee-calculator.js new file mode 100644 index 0000000000..dd3d14d8df --- /dev/null +++ b/web3.js/src/fee-calculator.js @@ -0,0 +1,13 @@ +// @flow + +/** + * @typedef {Object} FeeCalculator + * @property {number} lamportsPerSignature lamports Cost in lamports to validate a signature + * @property {number} targetLamportsPerSignature + * @property {number} targetSignaturesPerSlot + */ +export type FeeCalculator = { + lamportsPerSignature: number, + targetSignaturesPerSlot: number, + targetLamportsPerSignature: number, +}; diff --git a/web3.js/src/timing.js b/web3.js/src/timing.js index fc351e8712..ca76e74d36 100644 --- a/web3.js/src/timing.js +++ b/web3.js/src/timing.js @@ -1,6 +1,14 @@ // @flow -// These constants should match the values in -// https://github.com/solana-labs/solana/blob/master/sdk/src/timing.rs +// TODO: These constants should be removed in favor of reading them out of a +// Syscall account + +/** + * @ignore + */ export const NUM_TICKS_PER_SECOND = 10; + +/** + * @ignore + */ export const DEFAULT_TICKS_PER_SLOT = 8; diff --git a/web3.js/test/connection.test.js b/web3.js/test/connection.test.js index 9d2cb1a4b2..3ddef10341 100644 --- a/web3.js/test/connection.test.js +++ b/web3.js/test/connection.test.js @@ -204,8 +204,12 @@ test('get recent blockhash', async () => { mockGetRecentBlockhash(); - const recentBlockhash = await connection.getRecentBlockhash(); + const [ + recentBlockhash, + feeCalculator, + ] = await connection.getRecentBlockhash(); expect(recentBlockhash.length).toBeGreaterThanOrEqual(43); + expect(feeCalculator.lamportsPerSignature).toBeGreaterThanOrEqual(0); }); test('request airdrop', async () => { @@ -482,7 +486,9 @@ test('multi-instruction transaction', async () => { accountFrom.publicKey, accountTo.publicKey, 100, - ).add(SystemProgram.transfer(accountTo.publicKey, accountFrom.publicKey, 100)); + ).add( + SystemProgram.transfer(accountTo.publicKey, accountFrom.publicKey, 100), + ); const signature = await connection.sendTransaction( transaction, accountFrom, diff --git a/web3.js/test/mockrpc/get-recent-blockhash.js b/web3.js/test/mockrpc/get-recent-blockhash.js index f0e2f0f253..013d6a44eb 100644 --- a/web3.js/test/mockrpc/get-recent-blockhash.js +++ b/web3.js/test/mockrpc/get-recent-blockhash.js @@ -18,7 +18,7 @@ export function mockGetRecentBlockhash() { result: [ recentBlockhash.publicKey.toBase58(), { - /* empty fee calculator */ + lamportsPerSignature: 42, }, ], }, diff --git a/web3.js/test/transaction-payer.test.js b/web3.js/test/transaction-payer.test.js index dc1569a90b..a0a0882cc5 100644 --- a/web3.js/test/transaction-payer.test.js +++ b/web3.js/test/transaction-payer.test.js @@ -137,11 +137,11 @@ test('transaction-payer', async () => { }, ]); - // accountPayer should be less less than 100 as it paid for the transaction + // accountPayer could be less than 100 as it paid for the transaction // (exact amount less depends on the current cluster fees) const balance = await connection.getBalance(accountPayer.publicKey); expect(balance).toBeGreaterThan(0); - expect(balance).toBeLessThanOrEqual(99); + expect(balance).toBeLessThanOrEqual(100); // accountFrom should have exactly 2, since it didn't pay for the transaction mockRpc.push([