feat: add getSlotLeader()/getClusterNodes()
This commit is contained in:
@ -43,6 +43,13 @@ declare module '@solana/web3.js' {
|
|||||||
data: Buffer,
|
data: Buffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
declare export type ContactInfo = {
|
||||||
|
id: string,
|
||||||
|
gossip: string,
|
||||||
|
tpu: string | null,
|
||||||
|
rpc: string | null,
|
||||||
|
};
|
||||||
|
|
||||||
declare export type KeyedAccountInfo = {
|
declare export type KeyedAccountInfo = {
|
||||||
accountId: PublicKey,
|
accountId: PublicKey,
|
||||||
accountInfo: AccountInfo,
|
accountInfo: AccountInfo,
|
||||||
@ -62,9 +69,11 @@ declare module '@solana/web3.js' {
|
|||||||
|
|
||||||
declare export class Connection {
|
declare export class Connection {
|
||||||
constructor(endpoint: string): Connection;
|
constructor(endpoint: string): Connection;
|
||||||
getBalance(publicKey: PublicKey): Promise<number>;
|
|
||||||
getAccountInfo(publicKey: PublicKey): Promise<AccountInfo>;
|
getAccountInfo(publicKey: PublicKey): Promise<AccountInfo>;
|
||||||
|
getBalance(publicKey: PublicKey): Promise<number>;
|
||||||
|
getClusterNodes(): Promise<Array<ContactInfo>>;
|
||||||
confirmTransaction(signature: TransactionSignature): Promise<boolean>;
|
confirmTransaction(signature: TransactionSignature): Promise<boolean>;
|
||||||
|
getSlotLeader(): Promise<string>;
|
||||||
getSignatureStatus(
|
getSignatureStatus(
|
||||||
signature: TransactionSignature,
|
signature: TransactionSignature,
|
||||||
): Promise<SignatureSuccess | TransactionError | null>;
|
): Promise<SignatureSuccess | TransactionError | null>;
|
||||||
@ -102,7 +111,11 @@ declare module '@solana/web3.js' {
|
|||||||
space: number,
|
space: number,
|
||||||
programId: PublicKey,
|
programId: PublicKey,
|
||||||
): Transaction;
|
): Transaction;
|
||||||
static transfer(from: PublicKey, to: PublicKey, amount: number): Transaction;
|
static transfer(
|
||||||
|
from: PublicKey,
|
||||||
|
to: PublicKey,
|
||||||
|
amount: number,
|
||||||
|
): Transaction;
|
||||||
static assign(from: PublicKey, programId: PublicKey): Transaction;
|
static assign(from: PublicKey, programId: PublicKey): Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
47
web3.js/package-lock.json
generated
47
web3.js/package-lock.json
generated
@ -7522,8 +7522,7 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"aproba": {
|
"aproba": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
@ -7544,14 +7543,12 @@
|
|||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@ -7566,20 +7563,17 @@
|
|||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@ -7696,8 +7690,7 @@
|
|||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
@ -7709,7 +7702,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
@ -7724,7 +7716,6 @@
|
|||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
@ -7732,14 +7723,12 @@
|
|||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.2",
|
"safe-buffer": "^5.1.2",
|
||||||
"yallist": "^3.0.0"
|
"yallist": "^3.0.0"
|
||||||
@ -7758,7 +7747,6 @@
|
|||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
@ -7839,8 +7827,7 @@
|
|||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
@ -7852,7 +7839,6 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
@ -7938,8 +7924,7 @@
|
|||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"safer-buffer": {
|
"safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
@ -7975,7 +7960,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
@ -7995,7 +7979,6 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
@ -8039,14 +8022,12 @@
|
|||||||
"wrappy": {
|
"wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -8537,8 +8518,7 @@
|
|||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
|
||||||
"integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==",
|
"integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"hook-std": {
|
"hook-std": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
@ -8656,8 +8636,7 @@
|
|||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz",
|
||||||
"integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=",
|
"integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"acorn-globals": {
|
"acorn-globals": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
|
@ -17,6 +17,22 @@ import type {TransactionSignature} from './transaction';
|
|||||||
|
|
||||||
type RpcRequest = (methodName: string, args: Array<any>) => any;
|
type RpcRequest = (methodName: string, args: Array<any>) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information describing a cluster node
|
||||||
|
*
|
||||||
|
* @typedef {Object} ContactInfo
|
||||||
|
* @property {string} id Unique identifier of the node
|
||||||
|
* @property {string} gossip Gossip network address for the node
|
||||||
|
* @property {string} tpu TPU network address for the node (null if not available)
|
||||||
|
* @property {string|null} rpc JSON RPC network address for the node (null if not available)
|
||||||
|
*/
|
||||||
|
type ContactInfo = {
|
||||||
|
id: string,
|
||||||
|
gossip: string,
|
||||||
|
tpu: string | null,
|
||||||
|
rpc: string | null,
|
||||||
|
};
|
||||||
|
|
||||||
function createRpcRequest(url): RpcRequest {
|
function createRpcRequest(url): RpcRequest {
|
||||||
const server = jayson(async (request, callback) => {
|
const server = jayson(async (request, callback) => {
|
||||||
const options = {
|
const options = {
|
||||||
@ -120,6 +136,25 @@ const ProgramAccountNotificationResult = struct({
|
|||||||
*/
|
*/
|
||||||
const ConfirmTransactionRpcResult = jsonRpcResult('boolean');
|
const ConfirmTransactionRpcResult = jsonRpcResult('boolean');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expected JSON RPC response for the "getSlotLeader" message
|
||||||
|
*/
|
||||||
|
const GetSlotLeader = jsonRpcResult('string');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expected JSON RPC response for the "getClusterNodes" message
|
||||||
|
*/
|
||||||
|
const GetClusterNodes = jsonRpcResult(
|
||||||
|
struct.list([
|
||||||
|
struct({
|
||||||
|
id: 'string',
|
||||||
|
gossip: 'string',
|
||||||
|
tpu: struct.union(['null', 'string']),
|
||||||
|
rpc: struct.union(['null', 'string']),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expected JSON RPC response for the "getSignatureStatus" message
|
* Expected JSON RPC response for the "getSignatureStatus" message
|
||||||
*/
|
*/
|
||||||
@ -336,6 +371,32 @@ export class Connection {
|
|||||||
return res.result;
|
return res.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the current slot leader of the cluster
|
||||||
|
*/
|
||||||
|
async getSlotLeader(): Promise<string> {
|
||||||
|
const unsafeRes = await this._rpcRequest('getSlotLeader', []);
|
||||||
|
const res = GetSlotLeader(unsafeRes);
|
||||||
|
if (res.error) {
|
||||||
|
throw new Error(res.error.message);
|
||||||
|
}
|
||||||
|
assert(typeof res.result !== 'undefined');
|
||||||
|
return res.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the current slot leader of the cluster
|
||||||
|
*/
|
||||||
|
async getClusterNodes(): Promise<Array<ContactInfo>> {
|
||||||
|
const unsafeRes = await this._rpcRequest('getClusterNodes', []);
|
||||||
|
const res = GetClusterNodes(unsafeRes);
|
||||||
|
if (res.error) {
|
||||||
|
throw new Error(res.error.message);
|
||||||
|
}
|
||||||
|
assert(typeof res.result !== 'undefined');
|
||||||
|
return res.result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the current transaction count of the cluster
|
* Fetch the current transaction count of the cluster
|
||||||
*/
|
*/
|
||||||
|
@ -41,5 +41,7 @@ export async function sendAndConfirmRawTransaction(
|
|||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Raw transaction ${signature} failed (${JSON.stringify(status)})`);
|
throw new Error(
|
||||||
|
`Raw transaction ${signature} failed (${JSON.stringify(status)})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,9 @@ export async function sendAndConfirmTransaction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status && status.Err && !('AccountInUse' in status.Err)) {
|
if (status && status.Err && !('AccountInUse' in status.Err)) {
|
||||||
throw new Error(`Transaction ${signature} failed (${JSON.stringify(status)})`);
|
throw new Error(
|
||||||
|
`Transaction ${signature} failed (${JSON.stringify(status)})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retry in 0..100ms to try to avoid another AccountInUse collision
|
// Retry in 0..100ms to try to avoid another AccountInUse collision
|
||||||
|
@ -64,6 +64,64 @@ test('get balance', async () => {
|
|||||||
expect(balance).toBeGreaterThanOrEqual(0);
|
expect(balance).toBeGreaterThanOrEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('get slot leader', async () => {
|
||||||
|
const connection = new Connection(url);
|
||||||
|
|
||||||
|
mockRpc.push([
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
method: 'getSlotLeader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
error: null,
|
||||||
|
result: '11111111111111111111111111111111',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const slotLeader = await connection.getSlotLeader();
|
||||||
|
if (mockRpcEnabled) {
|
||||||
|
expect(slotLeader).toBe('11111111111111111111111111111111');
|
||||||
|
} else {
|
||||||
|
// No idea what the correct slotLeader value should be on a live cluster, so
|
||||||
|
// just check the type
|
||||||
|
expect(typeof slotLeader).toBe('string');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('get cluster nodes', async () => {
|
||||||
|
const connection = new Connection(url);
|
||||||
|
|
||||||
|
mockRpc.push([
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
method: 'getClusterNodes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
error: null,
|
||||||
|
result: [
|
||||||
|
{
|
||||||
|
id: '11111111111111111111111111111111',
|
||||||
|
gossip: '127.0.0.0:1234',
|
||||||
|
tpu: '127.0.0.0:1235',
|
||||||
|
rpc: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const clusterNodes = await connection.getClusterNodes();
|
||||||
|
if (mockRpcEnabled) {
|
||||||
|
expect(clusterNodes).toHaveLength(1);
|
||||||
|
expect(clusterNodes[0].id).toBe('11111111111111111111111111111111');
|
||||||
|
expect(typeof clusterNodes[0].gossip).toBe('string');
|
||||||
|
expect(typeof clusterNodes[0].tpu).toBe('string');
|
||||||
|
expect(clusterNodes[0].rpc).toBeNull();
|
||||||
|
} else {
|
||||||
|
// There should be at least one node (the node that we're talking to)
|
||||||
|
expect(clusterNodes.length).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('confirm transaction - error', () => {
|
test('confirm transaction - error', () => {
|
||||||
const connection = new Connection(url);
|
const connection = new Connection(url);
|
||||||
|
|
||||||
|
@ -23,7 +23,11 @@ test('load native program', async () => {
|
|||||||
|
|
||||||
const connection = new Connection(url);
|
const connection = new Connection(url);
|
||||||
const from = await newAccountWithLamports(connection, 1024);
|
const from = await newAccountWithLamports(connection, 1024);
|
||||||
const programId = await NativeLoader.load(connection, from, 'solana_noop_program');
|
const programId = await NativeLoader.load(
|
||||||
|
connection,
|
||||||
|
from,
|
||||||
|
'solana_noop_program',
|
||||||
|
);
|
||||||
const transaction = new Transaction().add({
|
const transaction = new Transaction().add({
|
||||||
keys: [{pubkey: from.publicKey, isSigner: true}],
|
keys: [{pubkey: from.publicKey, isSigner: true}],
|
||||||
programId,
|
programId,
|
||||||
|
@ -10,7 +10,11 @@ test('signPartial', () => {
|
|||||||
const account1 = new Account();
|
const account1 = new Account();
|
||||||
const account2 = new Account();
|
const account2 = new Account();
|
||||||
const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash
|
const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash
|
||||||
const transfer = SystemProgram.transfer(account1.publicKey, account2.publicKey, 123);
|
const transfer = SystemProgram.transfer(
|
||||||
|
account1.publicKey,
|
||||||
|
account2.publicKey,
|
||||||
|
123,
|
||||||
|
);
|
||||||
|
|
||||||
const transaction = new Transaction({recentBlockhash}).add(transfer);
|
const transaction = new Transaction({recentBlockhash}).add(transfer);
|
||||||
transaction.sign(account1, account2);
|
transaction.sign(account1, account2);
|
||||||
@ -27,10 +31,21 @@ test('transfer signatures', () => {
|
|||||||
const account1 = new Account();
|
const account1 = new Account();
|
||||||
const account2 = new Account();
|
const account2 = new Account();
|
||||||
const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash
|
const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash
|
||||||
const transfer1 = SystemProgram.transfer(account1.publicKey, account2.publicKey, 123);
|
const transfer1 = SystemProgram.transfer(
|
||||||
const transfer2 = SystemProgram.transfer(account2.publicKey, account1.publicKey, 123);
|
account1.publicKey,
|
||||||
|
account2.publicKey,
|
||||||
|
123,
|
||||||
|
);
|
||||||
|
const transfer2 = SystemProgram.transfer(
|
||||||
|
account2.publicKey,
|
||||||
|
account1.publicKey,
|
||||||
|
123,
|
||||||
|
);
|
||||||
|
|
||||||
const orgTransaction = new Transaction({recentBlockhash}).add(transfer1, transfer2);
|
const orgTransaction = new Transaction({recentBlockhash}).add(
|
||||||
|
transfer1,
|
||||||
|
transfer2,
|
||||||
|
);
|
||||||
orgTransaction.sign(account1, account2);
|
orgTransaction.sign(account1, account2);
|
||||||
|
|
||||||
const newTransaction = new Transaction({
|
const newTransaction = new Transaction({
|
||||||
|
Reference in New Issue
Block a user