feat: add getStakeActivation (#12274)

* feat: add getStakeActivation

* chore: add rollup watch

* feat: use string literal for stake activation state

* fix: remove optional chaining due to issue with esdoc

* chore: remove optional_chaining

* feat: add live test for getStakeActivation

* feat: extend _buildArgs to support additional options, simplify unit test
This commit is contained in:
Bartosz Lipinski 2020-09-17 01:50:13 -05:00 committed by GitHub
parent 8d6af087a2
commit 63db4759f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 212 additions and 1 deletions

11
web3.js/module.d.ts vendored
View File

@ -197,6 +197,12 @@ declare module '@solana/web3.js' {
space: number;
};
export type StakeActivationData = {
state: 'active' | 'inactive' | 'activating' | 'deactivating';
active: number;
inactive: number;
};
export type KeyedAccountInfo = {
accountId: PublicKey;
accountInfo: AccountInfo<Buffer>;
@ -314,6 +320,11 @@ declare module '@solana/web3.js' {
): Promise<
RpcResponseAndContext<AccountInfo<Buffer | ParsedAccountData> | null>
>;
getStakeActivation(
publicKey: PublicKey,
commitment?: Commitment,
epoch?: number,
): Promise<StakeActivationData>;
getProgramAccounts(
programId: PublicKey,
commitment?: Commitment,

View File

@ -178,6 +178,12 @@ declare module '@solana/web3.js' {
space: number,
};
declare export type StakeActivationData = {
state: 'active' | 'inactive' | 'activating' | 'deactivating',
active: number,
inactive: number,
};
declare export type ParsedMessageAccount = {
pubkey: PublicKey,
signer: boolean,
@ -328,6 +334,11 @@ declare module '@solana/web3.js' {
): Promise<
RpcResponseAndContext<AccountInfo<Buffer | ParsedAccountData> | null>,
>;
getStakeActivation(
publicKey: PublicKey,
commitment?: Commitment,
epoch?: number,
): Promise<StakeActivationData>;
getProgramAccounts(
programId: PublicKey,
commitment: ?Commitment,

View File

@ -806,6 +806,20 @@ const ParsedAccountInfoResult = struct.object({
rentEpoch: 'number?',
});
/**
* @private
*/
const StakeActivationResult = struct.object({
state: struct.union([
struct.literal('active'),
struct.literal('inactive'),
struct.literal('activating'),
struct.literal('deactivating'),
]),
active: 'number',
inactive: 'number',
});
/**
* Expected JSON RPC response for the "getAccountInfo" message
*/
@ -820,6 +834,11 @@ const GetParsedAccountInfoResult = jsonRpcResultAndContext(
struct.union(['null', ParsedAccountInfoResult]),
);
/**
* Expected JSON RPC response for the "getStakeActivation" message with jsonParsed param
*/
const GetStakeActivationResult = jsonRpcResult(StakeActivationResult);
/**
* Expected JSON RPC response for the "getConfirmedSignaturesForAddress" message
*/
@ -1201,6 +1220,20 @@ type ParsedAccountData = {
space: number,
};
/**
* Stake Activation data
*
* @typedef {Object} StakeActivationData
* @property {string} state: <string - the stake account's activation state, one of: active, inactive, activating, deactivating
* @property {number} active: stake active during the epoch
* @property {number} inactive: stake inactive during the epoch
*/
type StakeActivationData = {
state: 'active' | 'inactive' | 'activating' | 'deactivating',
active: number,
inactive: number,
};
/**
* Information describing an account
*
@ -1857,6 +1890,36 @@ export class Connection {
});
}
/**
* Returns epoch activation information for a stake account that has been delegated
*/
async getStakeActivation(
publicKey: PublicKey,
commitment: ?Commitment,
epoch: ?number,
): Promise<StakeActivationData> {
const args = this._buildArgs(
[publicKey.toBase58()],
commitment,
undefined,
epoch !== undefined ? {epoch} : undefined,
);
const unsafeRes = await this._rpcRequest('getStakeActivation', args);
const res = GetStakeActivationResult(unsafeRes);
if (res.error) {
throw new Error(
`failed to get Stake Activation ${publicKey.toBase58()}: ${
res.error.message
}`,
);
}
assert(typeof res.result !== 'undefined');
const {state, active, inactive} = res.result;
return {state, active, inactive};
}
/**
* Fetch all the accounts owned by the specified program id
*
@ -3093,9 +3156,10 @@ export class Connection {
args: Array<any>,
override: ?Commitment,
encoding?: 'jsonParsed' | 'base64',
extra?: any,
): Array<any> {
const commitment = override || this._commitment;
if (commitment || encoding) {
if (commitment || encoding || extra) {
let options: any = {};
if (encoding) {
options.encoding = encoding;
@ -3103,6 +3167,9 @@ export class Connection {
if (commitment) {
options.commitment = commitment;
}
if (extra) {
options = Object.assign(options, extra);
}
args.push(options);
}
return args;

View File

@ -4,12 +4,15 @@ import {Token, u64} from '@solana/spl-token';
import {
Account,
Authorized,
Connection,
SystemProgram,
Transaction,
sendAndConfirmTransaction,
LAMPORTS_PER_SOL,
Lockup,
PublicKey,
StakeProgram,
} from '../src';
import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from '../src/timing';
import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
@ -1521,6 +1524,125 @@ test('get largest accounts', async () => {
expect(largestAccounts.length).toEqual(20);
});
test('stake activation should throw when called for not delegated account', async () => {
const connection = new Connection(url);
const publicKey = new Account().publicKey;
mockRpc.push([
url,
{
method: 'getStakeActivation',
params: [publicKey.toBase58(), {}],
},
{
error: {message: 'account not delegated'},
result: undefined,
},
]);
await expect(connection.getStakeActivation(publicKey)).rejects.toThrow();
});
test('stake activation should return activating for new accounts', async () => {
if (mockRpcEnabled) {
console.log('non-live test skipped');
return;
}
const connection = new Connection(url, 'recent');
const voteAccounts = await connection.getVoteAccounts();
const voteAccount = voteAccounts.current.concat(voteAccounts.delinquent)[0];
const votePubkey = new PublicKey(voteAccount.votePubkey);
const authorized = new Account();
await connection.requestAirdrop(authorized.publicKey, 2 * LAMPORTS_PER_SOL);
const minimumAmount = await connection.getMinimumBalanceForRentExemption(
StakeProgram.space,
'recent',
);
const newStakeAccount = new Account();
let createAndInitialize = StakeProgram.createAccount({
fromPubkey: authorized.publicKey,
stakePubkey: newStakeAccount.publicKey,
authorized: new Authorized(authorized.publicKey, authorized.publicKey),
lockup: new Lockup(0, 0, new PublicKey(0)),
lamports: minimumAmount + 42,
});
await sendAndConfirmTransaction(
connection,
createAndInitialize,
[authorized, newStakeAccount],
{commitment: 'single', skipPreflight: true},
);
let delegation = StakeProgram.delegate({
stakePubkey: newStakeAccount.publicKey,
authorizedPubkey: authorized.publicKey,
votePubkey,
});
await sendAndConfirmTransaction(connection, delegation, [authorized], {
commitment: 'single',
skipPreflight: true,
});
const LARGE_EPOCH = 4000;
await expect(
connection.getStakeActivation(
newStakeAccount.publicKey,
'recent',
LARGE_EPOCH,
),
).rejects.toThrow(
`failed to get Stake Activation ${newStakeAccount.publicKey.toBase58()}: Invalid param: epoch ${LARGE_EPOCH} has not yet started`,
);
const activationState = await connection.getStakeActivation(
newStakeAccount.publicKey,
);
expect(activationState.state).toBe('activating');
expect(activationState.inactive).toBe(42);
expect(activationState.active).toBe(0);
});
test('stake activation should only accept state with valid string literals', async () => {
if (!mockRpcEnabled) {
console.log('live test skipped');
return;
}
const connection = new Connection(url, 'recent');
const publicKey = new Account().publicKey;
const addStakeActivationMock = state => {
mockRpc.push([
url,
{
method: 'getStakeActivation',
params: [publicKey.toBase58(), {}],
},
{
error: undefined,
result: {
state: state,
active: 0,
inactive: 80,
},
},
]);
};
addStakeActivationMock('active');
let activation = await connection.getStakeActivation(publicKey);
expect(activation.state).toBe('active');
expect(activation.active).toBe(0);
expect(activation.inactive).toBe(80);
addStakeActivationMock('invalid');
await expect(connection.getStakeActivation(publicKey)).rejects.toThrow();
});
test('getVersion', async () => {
const connection = new Connection(url);