doc comments

This commit is contained in:
gallynaut 2022-11-30 21:02:57 -07:00
parent 0107e1354f
commit d753581a99
7 changed files with 167 additions and 203 deletions

View File

@ -60,7 +60,7 @@ async function main() {
);
execSync(
'rm -rf ./src/generated && npx anchor-client-gen --program-id SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f ./src/idl/mainnet.json ./src/generated'
'rm -rf ./src/generated && npx anchor-client-gen --program-id SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f ../../../switchboard-core/switchboard_v2/target/idl/switchboard_v2.json ./src/generated'
);
fs.writeFileSync(
'./src/generated/index.ts',

View File

@ -24,6 +24,7 @@ import { PermissionAccount } from './permissionAccount';
import * as spl from '@solana/spl-token';
import { TransactionObject } from '../transaction';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { AggregatorHistoryBuffer } from './aggregatorHistoryBuffer';
/**
* Account type holding a data feed's update configuration, job accounts, and its current result.
@ -40,6 +41,8 @@ import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
export class AggregatorAccount extends Account<types.AggregatorAccountData> {
static accountName = 'AggregatorAccountData';
public history?: AggregatorHistoryBuffer;
/**
* Returns the aggregator's name buffer in a stringified format.
*/
@ -96,6 +99,7 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
this.publicKey
);
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
this.history = AggregatorHistoryBuffer.fromAggregator(this.program, data);
return data;
}
@ -224,63 +228,6 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
return [txnSignature, account];
}
/**
* Decode an aggregators history buffer and return an array of historical samples
* @params historyBuffer the historyBuffer AccountInfo stored on-chain
* @return the array of {@linkcode types.AggregatorHistoryRow} samples
*/
public static decodeHistory(
bufferAccountInfo: AccountInfo<Buffer>
): Array<types.AggregatorHistoryRow> {
const historyBuffer = bufferAccountInfo.data ?? Buffer.from('');
const ROW_SIZE = 28;
if (historyBuffer.length < 12) {
return [];
}
const insertIdx = historyBuffer.readUInt32LE(8) * ROW_SIZE;
const front: Array<types.AggregatorHistoryRow> = [];
const tail: Array<types.AggregatorHistoryRow> = [];
for (let i = 12; i < historyBuffer.length; i += ROW_SIZE) {
if (i + ROW_SIZE > historyBuffer.length) {
break;
}
const row = types.AggregatorHistoryRow.fromDecoded(
types.AggregatorHistoryRow.layout().decode(historyBuffer, i)
);
if (row.timestamp.eq(new anchor.BN(0))) {
break;
}
if (i <= insertIdx) {
tail.push(row);
} else {
front.push(row);
}
}
return front.concat(tail);
}
/**
* Fetch an aggregators history buffer and return an array of historical samples
* @params aggregator the pre-loaded aggregator state
* @return the array of {@linkcode types.AggregatorHistoryRow} samples
*/
public async loadHistory(
aggregator: types.AggregatorAccountData
): Promise<Array<types.AggregatorHistoryRow>> {
if (PublicKey.default.equals(aggregator.historyBuffer)) {
return [];
}
const bufferAccountInfo = await this.program.connection.getAccountInfo(
aggregator.historyBuffer
);
if (bufferAccountInfo === null) {
throw new errors.AccountNotFoundError(aggregator.historyBuffer);
}
return AggregatorAccount.decodeHistory(bufferAccountInfo);
}
public getAccounts(params: {
queueAccount: QueueAccount;
queueAuthority: PublicKey;
@ -570,62 +517,6 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
return txnSignature;
}
static getHistoryBufferSize(samples: number): number {
return 8 + 4 + samples * 28;
}
public async setHistoryBufferInstruction(
payer: PublicKey,
params: {
size: number;
authority?: Keypair;
buffer?: Keypair;
}
): Promise<TransactionObject> {
const buffer = params.buffer ?? Keypair.generate();
const ixns: TransactionInstruction[] = [];
const signers: Keypair[] = params.authority
? [params.authority, buffer]
: [buffer];
const size = AggregatorAccount.getHistoryBufferSize(params.size);
ixns.push(
SystemProgram.createAccount({
fromPubkey: payer,
newAccountPubkey: buffer.publicKey,
space: size,
lamports:
await this.program.connection.getMinimumBalanceForRentExemption(size),
programId: this.program.programId,
}),
types.aggregatorSetHistoryBuffer(
this.program,
{ params: {} },
{
aggregator: this.publicKey,
authority: params.authority ? params.authority.publicKey : payer,
buffer: buffer.publicKey,
}
)
);
return new TransactionObject(payer, ixns, signers);
}
public async setHistoryBuffer(params: {
size: number;
authority?: Keypair;
buffer?: Keypair;
}): Promise<TransactionSignature> {
const setHistoryTxn = await this.setHistoryBufferInstruction(
this.program.walletPubkey,
params
);
const txnSignature = await this.program.signAndSend(setHistoryTxn);
return txnSignature;
}
public setQueueInstruction(
payer: PublicKey,
params: {
@ -1282,13 +1173,40 @@ export interface AggregatorInitParams {
}
export type AggregatorSetConfigParams = Partial<{
name: Buffer;
metadata: Buffer;
/**
* Name of the aggregator to store on-chain.
*/
name: string;
/**
* Metadata of the aggregator to store on-chain.
*/
metadata: string;
/**
* Number of oracles to request on aggregator update.
*/
batchSize: number;
/**
* Minimum number of oracle responses required before a round is validated.
*/
minOracleResults: number;
/**
* Minimum number of feed jobs suggested to be successful before an oracle
* sends a response.
*/
minJobResults: number;
/**
* Minimum number of seconds required between aggregator rounds.
*/
minUpdateDelaySeconds: number;
/**
* Number of seconds for which, even if the variance threshold is not passed,
* accept new responses from oracles.
*/
forceReportPeriod: number;
/**
* Change percentage required between a previous round and the current round.
* If variance percentage is not met, reject new oracle responses.
*/
varianceThreshold: number;
}>;
@ -1315,11 +1233,11 @@ export interface AggregatorOpenRoundParams {
* Parameters for creating and setting a history buffer for an aggregator
*/
export interface AggregatorSetHistoryBufferParams {
/*
/**
* Authority keypair for the aggregator.
*/
authority?: Keypair;
/*
/**
* Number of elements for the history buffer to fit.
*/
size: number;

View File

@ -80,7 +80,7 @@ export class AggregatorHistoryBuffer extends Account<
program: SwitchboardProgram,
aggregator: types.AggregatorAccountData
): AggregatorHistoryBuffer | undefined {
if (aggregator.historyBuffer.equals(aggregator.historyBuffer)) {
if (aggregator.historyBuffer.equals(PublicKey.default)) {
return undefined;
}

View File

@ -15,6 +15,7 @@ import { SwitchboardProgram } from '../program';
import { TransactionObject } from '../transaction';
import { Account, OnAccountChangeCallback } from './account';
import { AggregatorAccount } from './aggregatorAccount';
import { CrankDataBuffer } from './crankDataBuffer';
import { QueueAccount } from './queueAccount';
/**
@ -27,7 +28,7 @@ export class CrankAccount extends Account<types.CrankAccountData> {
static accountName = 'CrankAccountData';
/** The public key of the crank's data buffer storing a priority queue of {@linkcode AggregatorAccount}'s and their next available update timestamp */
dataBuffer?: PublicKey;
dataBuffer?: CrankDataBuffer;
/**
* Get the size of an {@linkcode CrankAccount} on-chain.
@ -51,30 +52,6 @@ export class CrankAccount extends Account<types.CrankAccountData> {
);
}
/**
* Invoke a callback each time a crank's buffer has changed on-chain. The buffer stores a list of {@linkcode AggregatorAccount} public keys along with their next available update time.
* @param callback - the callback invoked when the crank's buffer changes
* @param commitment - optional, the desired transaction finality. defaults to 'confirmed'
* @returns the websocket subscription id
*/
onBufferChange(
callback: OnAccountChangeCallback<Array<types.CrankRow>>,
_dataBuffer?: PublicKey,
commitment: Commitment = 'confirmed'
): number {
const buffer = this.dataBuffer ?? _dataBuffer;
if (!buffer) {
throw new Error(
`No crank dataBuffer provided. Call crankAccount.loadData() or pass it to this function in order to watch the account for changes`
);
}
return this.program.connection.onAccountChange(
buffer,
accountInfo => callback(CrankAccount.decodeBuffer(accountInfo)),
commitment
);
}
/**
* Retrieve and decode the {@linkcode types.CrankAccountData} stored in this account.
*/
@ -84,59 +61,10 @@ export class CrankAccount extends Account<types.CrankAccountData> {
this.publicKey
);
if (data === null) throw new errors.AccountNotFoundError(this.publicKey);
this.dataBuffer = data.dataBuffer;
this.dataBuffer = CrankDataBuffer.fromCrank(this.program, data);
return data;
}
public static decodeBuffer(
bufferAccountInfo: AccountInfo<Buffer>
): Array<types.CrankRow> {
const buffer = bufferAccountInfo.data.slice(8) ?? Buffer.from('');
const maxRows = Math.floor(buffer.byteLength / 40);
const pqData: Array<types.CrankRow> = [];
for (let i = 0; i < maxRows * 40; i += 40) {
if (buffer.byteLength - i < 40) {
break;
}
const rowBuf = buffer.slice(i, i + 40);
const pubkey = new PublicKey(rowBuf.slice(0, 32));
if (pubkey.equals(PublicKey.default)) {
break;
}
const nextTimestamp = new anchor.BN(rowBuf.slice(32, 40), 'le');
pqData.push(new types.CrankRow({ pubkey, nextTimestamp }));
}
return pqData;
}
public async loadCrank(
sorted = false,
commitment: Commitment = 'confirmed'
): Promise<Array<types.CrankRow>> {
// Can we do this in a single RPC call? Do we need pqSize?
const dataBuffer = this.dataBuffer ?? (await this.loadData()).dataBuffer;
const bufferAccountInfo = await this.program.connection.getAccountInfo(
dataBuffer,
{ commitment }
);
if (bufferAccountInfo === null) {
throw new errors.AccountNotFoundError(dataBuffer);
}
const pqData = CrankAccount.decodeBuffer(bufferAccountInfo);
if (sorted) {
return pqData.sort((a, b) => a.nextTimestamp.cmp(b.nextTimestamp));
}
return pqData;
}
public static async createInstructions(
program: SwitchboardProgram,
payer: PublicKey,
@ -319,7 +247,8 @@ export class CrankAccount extends Account<types.CrankAccountData> {
lease: leaseAccount.publicKey,
escrow: leaseEscrow,
programState: this.program.programState.publicKey,
dataBuffer: this.dataBuffer ?? (await this.loadData()).dataBuffer,
dataBuffer:
this.dataBuffer?.publicKey ?? (await this.loadData()).dataBuffer,
}
),
],
@ -347,7 +276,15 @@ export class CrankAccount extends Account<types.CrankAccountData> {
* @return List of {@linkcode types.CrankRow}, ordered by timestamp.
*/
async peakNextWithTime(num?: number): Promise<types.CrankRow[]> {
const crankRows = await this.loadCrank(true);
let crank: CrankDataBuffer;
if (this.dataBuffer) {
crank = this.dataBuffer;
} else {
const crankData = await this.loadData();
crank = new CrankDataBuffer(this.program, crankData.dataBuffer);
}
const crankRows = await crank.loadData();
return crankRows.slice(0, num ?? crankRows.length);
}
@ -380,7 +317,14 @@ export class CrankAccount extends Account<types.CrankAccountData> {
pubkey: PublicKey,
crankRows?: Array<types.CrankRow>
): Promise<boolean> {
const rows = crankRows ?? (await this.loadCrank());
let crank: CrankDataBuffer;
if (this.dataBuffer) {
crank = this.dataBuffer;
} else {
const crankData = await this.loadData();
crank = new CrankDataBuffer(this.program, crankData.dataBuffer);
}
const rows = await crank.loadData();
const idx = rows.findIndex(r => r.pubkey.equals(pubkey));
if (idx === -1) {

View File

@ -0,0 +1,99 @@
import * as anchor from '@project-serum/anchor';
import { AccountInfo, Commitment, PublicKey } from '@solana/web3.js';
import * as errors from '../errors';
import * as types from '../generated';
import { SwitchboardProgram } from '../program';
import { Account, OnAccountChangeCallback } from './account';
/**
* Account holding a priority queue of aggregators and their next available update time.
*
* Data: Array<{@linkcode types.CrankRow}>
*/
export class CrankDataBuffer extends Account<Array<types.CrankRow>> {
static accountName = 'CrankDataBuffer';
public size = 0;
/**
* Invoke a callback each time a crank's buffer has changed on-chain. The buffer stores a list of {@linkcode AggregatorAccount} public keys along with their next available update time.
* @param callback - the callback invoked when the crank's buffer changes
* @param commitment - optional, the desired transaction finality. defaults to 'confirmed'
* @returns the websocket subscription id
*/
onChange(
callback: OnAccountChangeCallback<Array<types.CrankRow>>,
commitment: Commitment = 'confirmed'
): number {
if (this.publicKey.equals(PublicKey.default)) {
throw new Error(
`No crank dataBuffer provided. Call crankAccount.loadData() or pass it to this function in order to watch the account for changes`
);
}
return this.program.connection.onAccountChange(
this.publicKey,
accountInfo => callback(CrankDataBuffer.decode(accountInfo)),
commitment
);
}
/**
* Retrieve and decode the {@linkcode types.CrankAccountData} stored in this account.
*/
public async loadData(): Promise<Array<types.CrankRow>> {
if (this.publicKey.equals(PublicKey.default)) {
return [];
}
const accountInfo = await this.program.connection.getAccountInfo(
this.publicKey
);
if (accountInfo === null)
throw new errors.AccountNotFoundError(this.publicKey);
const data = CrankDataBuffer.decode(accountInfo);
return data;
}
public static decode(
bufferAccountInfo: AccountInfo<Buffer>
): Array<types.CrankRow> {
const buffer = bufferAccountInfo.data.slice(8) ?? Buffer.from('');
const maxRows = Math.floor(buffer.byteLength / 40);
const pqData: Array<types.CrankRow> = [];
for (let i = 0; i < maxRows * 40; i += 40) {
if (buffer.byteLength - i < 40) {
break;
}
const rowBuf = buffer.slice(i, i + 40);
const pubkey = new PublicKey(rowBuf.slice(0, 32));
if (pubkey.equals(PublicKey.default)) {
break;
}
const nextTimestamp = new anchor.BN(rowBuf.slice(32, 40), 'le');
pqData.push(new types.CrankRow({ pubkey, nextTimestamp }));
}
return pqData;
}
static getDataBufferSize(size: number): number {
return 8 + size * 40;
}
/**
* Return an aggregator's assigned history buffer or undefined if it doesn't exist.
*/
static fromCrank(
program: SwitchboardProgram,
crank: types.CrankAccountData
): CrankDataBuffer {
if (crank.dataBuffer.equals(PublicKey.default)) {
throw new Error(`Failed to find crank data buffer`);
}
return new CrankDataBuffer(program, crank.dataBuffer);
}
}

View File

@ -1,7 +1,9 @@
export * from './account';
export * from './aggregatorAccount';
export * from './aggregatorHistoryBuffer';
export * from './bufferRelayAccount';
export * from './crankAccount';
export * from './crankDataBuffer';
export * from './jobAccount';
export * from './leaseAccount';
export * from './oracleAccount';

View File

@ -20,6 +20,7 @@ import { SwitchboardProgram } from '../program';
import { TransactionObject } from '../transaction';
import { Account, OnAccountChangeCallback } from './account';
import { AggregatorAccount, AggregatorInitParams } from './aggregatorAccount';
import { AggregatorHistoryBuffer } from './aggregatorHistoryBuffer';
import { BufferRelayerAccount, BufferRelayerInit } from './bufferRelayAccount';
import { CrankAccount, CrankInitParams } from './crankAccount';
import { JobAccount, JobInitParams } from './jobAccount';
@ -550,12 +551,12 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
}
if (params.historyLimit && params.historyLimit > 0) {
post.push(
await aggregatorAccount.setHistoryBufferInstruction(
this.program.walletPubkey,
{ size: params.historyLimit, authority: params.authority }
)
);
const [historyBufferInit, historyBuffer] =
await AggregatorHistoryBuffer.createInstructions(this.program, payer, {
aggregatorAccount,
maxSamples: params.historyLimit,
});
post.push(historyBufferInit);
}
const packed = TransactionObject.pack([