feat: add getSlotLeader()/getClusterNodes()
This commit is contained in:
parent
5f2bcd5200
commit
a2cd9180b5
|
@ -43,6 +43,13 @@ declare module '@solana/web3.js' {
|
|||
data: Buffer,
|
||||
};
|
||||
|
||||
declare export type ContactInfo = {
|
||||
id: string,
|
||||
gossip: string,
|
||||
tpu: string | null,
|
||||
rpc: string | null,
|
||||
};
|
||||
|
||||
declare export type KeyedAccountInfo = {
|
||||
accountId: PublicKey,
|
||||
accountInfo: AccountInfo,
|
||||
|
@ -62,9 +69,11 @@ declare module '@solana/web3.js' {
|
|||
|
||||
declare export class Connection {
|
||||
constructor(endpoint: string): Connection;
|
||||
getBalance(publicKey: PublicKey): Promise<number>;
|
||||
getAccountInfo(publicKey: PublicKey): Promise<AccountInfo>;
|
||||
getBalance(publicKey: PublicKey): Promise<number>;
|
||||
getClusterNodes(): Promise<Array<ContactInfo>>;
|
||||
confirmTransaction(signature: TransactionSignature): Promise<boolean>;
|
||||
getSlotLeader(): Promise<string>;
|
||||
getSignatureStatus(
|
||||
signature: TransactionSignature,
|
||||
): Promise<SignatureSuccess | TransactionError | null>;
|
||||
|
@ -102,7 +111,11 @@ declare module '@solana/web3.js' {
|
|||
space: number,
|
||||
programId: PublicKey,
|
||||
): 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -7522,8 +7522,7 @@
|
|||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
|
@ -7544,14 +7543,12 @@
|
|||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -7566,20 +7563,17 @@
|
|||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
|
@ -7696,8 +7690,7 @@
|
|||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
@ -7709,7 +7702,6 @@
|
|||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
|
@ -7724,7 +7716,6 @@
|
|||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
@ -7732,14 +7723,12 @@
|
|||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
|
@ -7758,7 +7747,6 @@
|
|||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
|
@ -7839,8 +7827,7 @@
|
|||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
|
@ -7852,7 +7839,6 @@
|
|||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -7938,8 +7924,7 @@
|
|||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
|
@ -7975,7 +7960,6 @@
|
|||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
|
@ -7995,7 +7979,6 @@
|
|||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
|
@ -8039,14 +8022,12 @@
|
|||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -8537,8 +8518,7 @@
|
|||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
|
||||
"integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"hook-std": {
|
||||
"version": "1.2.0",
|
||||
|
@ -8656,8 +8636,7 @@
|
|||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz",
|
||||
"integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"acorn-globals": {
|
||||
"version": "1.0.9",
|
||||
|
|
|
@ -17,6 +17,22 @@ import type {TransactionSignature} from './transaction';
|
|||
|
||||
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 {
|
||||
const server = jayson(async (request, callback) => {
|
||||
const options = {
|
||||
|
@ -120,6 +136,25 @@ const ProgramAccountNotificationResult = struct({
|
|||
*/
|
||||
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
|
||||
*/
|
||||
|
@ -336,6 +371,32 @@ export class Connection {
|
|||
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
|
||||
*/
|
||||
|
|
|
@ -41,5 +41,7 @@ export async function sendAndConfirmRawTransaction(
|
|||
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)) {
|
||||
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
|
||||
|
|
|
@ -64,6 +64,64 @@ test('get balance', async () => {
|
|||
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', () => {
|
||||
const connection = new Connection(url);
|
||||
|
||||
|
|
|
@ -23,7 +23,11 @@ test('load native program', async () => {
|
|||
|
||||
const connection = new Connection(url);
|
||||
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({
|
||||
keys: [{pubkey: from.publicKey, isSigner: true}],
|
||||
programId,
|
||||
|
|
|
@ -10,7 +10,11 @@ test('signPartial', () => {
|
|||
const account1 = new Account();
|
||||
const account2 = new Account();
|
||||
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);
|
||||
transaction.sign(account1, account2);
|
||||
|
@ -27,10 +31,21 @@ test('transfer signatures', () => {
|
|||
const account1 = new Account();
|
||||
const account2 = new Account();
|
||||
const recentBlockhash = account1.publicKey.toBase58(); // Fake recentBlockhash
|
||||
const transfer1 = SystemProgram.transfer(account1.publicKey, account2.publicKey, 123);
|
||||
const transfer2 = SystemProgram.transfer(account2.publicKey, account1.publicKey, 123);
|
||||
const transfer1 = SystemProgram.transfer(
|
||||
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);
|
||||
|
||||
const newTransaction = new Transaction({
|
||||
|
|
Loading…
Reference in New Issue