feat: add getSlotLeader()/getClusterNodes()

This commit is contained in:
Michael Vines 2019-04-23 09:53:26 -07:00
parent 5f2bcd5200
commit a2cd9180b5
8 changed files with 177 additions and 43 deletions

View File

@ -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;
}

View File

@ -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",

View File

@ -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
*/

View File

@ -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)})`,
);
}

View File

@ -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

View File

@ -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);

View File

@ -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,

View File

@ -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({