doc comments
This commit is contained in:
parent
0107e1354f
commit
d753581a99
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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([
|
||||
|
|
Loading…
Reference in New Issue