parent
872375877d
commit
0107e1354f
|
@ -26,9 +26,11 @@ import { TransactionObject } from '../transaction';
|
|||
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
||||
|
||||
/**
|
||||
* @class AggregatorAccount
|
||||
* Account type holding a data feed's update configuration, job accounts, and its current result.
|
||||
*
|
||||
* Data: {@linkcode types.AggregatorAccountData}
|
||||
* HistoryBuffer?: Array<{@linkcode types.AggregatorHistoryRow}>
|
||||
*
|
||||
* An aggregator account belongs to a single {@linkcode QueueAccount} but can later be transferred by the aggregator's authority. In order for an {@linkcode OracleAccount} to respond to an aggregator's update request, the aggregator must initialize a {@linkcode PermissionAccount} and {@linkcode LeaseAccount}. These will need to be recreated when transferring queues.
|
||||
*
|
||||
* Optionally, An aggregator can be pushed onto a {@linkcode CrankAccount} in order to be updated
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
import * as types from '../generated';
|
||||
import * as anchor from '@project-serum/anchor';
|
||||
import { Account, OnAccountChangeCallback } from './account';
|
||||
import * as errors from '../errors';
|
||||
import { SwitchboardProgram } from '../program';
|
||||
import {
|
||||
Commitment,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
TransactionInstruction,
|
||||
TransactionSignature,
|
||||
} from '@solana/web3.js';
|
||||
import { TransactionObject } from '../transaction';
|
||||
import { AggregatorAccount } from './aggregatorAccount';
|
||||
|
||||
export interface AggregatorHistoryInit {
|
||||
/** Aggregator account to add a history buffer for. */
|
||||
aggregatorAccount: AggregatorAccount;
|
||||
/** Maximum number of samples to store in a round robin history buffer. */
|
||||
maxSamples: number;
|
||||
/** Alternative keypair that is the authority for the aggregatorAccount and authorized to add a historyBuffer. */
|
||||
aggregatorAuthority?: Keypair;
|
||||
/** Existing keypair to create the history buffer for. Must be a fresh keypair not tied to an existing on-chain account. */
|
||||
keypair?: Keypair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Account type representing a round robin buffer of historical samples.
|
||||
*
|
||||
* Data: Array<{@linkcode types.AggregatorHistoryRow}>
|
||||
*/
|
||||
export class AggregatorHistoryBuffer extends Account<
|
||||
Array<types.AggregatorHistoryRow>
|
||||
> {
|
||||
static accountName = 'AggregatorHistoryBuffer';
|
||||
|
||||
public size = 28;
|
||||
|
||||
/**
|
||||
* 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 decode(
|
||||
historyBuffer: Buffer
|
||||
): Array<types.AggregatorHistoryRow> {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an aggregator's assigned history buffer or undefined if it doesn't exist.
|
||||
*/
|
||||
static fromAggregator(
|
||||
program: SwitchboardProgram,
|
||||
aggregator: types.AggregatorAccountData
|
||||
): AggregatorHistoryBuffer | undefined {
|
||||
if (aggregator.historyBuffer.equals(aggregator.historyBuffer)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new AggregatorHistoryBuffer(program, aggregator.historyBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 decode(historyBuffer: Buffer): Array<types.AggregatorHistoryRow> {
|
||||
return AggregatorHistoryBuffer.decode(historyBuffer);
|
||||
}
|
||||
|
||||
static getHistoryBufferSize(maxSamples: number): number {
|
||||
return 8 + 4 + maxSamples * 28;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 loadData(): Promise<Array<types.AggregatorHistoryRow>> {
|
||||
if (PublicKey.default.equals(this.publicKey)) {
|
||||
return [];
|
||||
}
|
||||
const bufferAccountInfo = await this.program.connection.getAccountInfo(
|
||||
this.publicKey
|
||||
);
|
||||
if (bufferAccountInfo === null) {
|
||||
throw new errors.AccountNotFoundError(this.publicKey);
|
||||
}
|
||||
return AggregatorHistoryBuffer.decode(bufferAccountInfo.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a callback each time an AggregatorAccount's data has changed on-chain.
|
||||
* @param callback - the callback invoked when the aggregator state changes
|
||||
* @param commitment - optional, the desired transaction finality. defaults to 'confirmed'
|
||||
* @returns the websocket subscription id
|
||||
*/
|
||||
public onChange(
|
||||
callback: OnAccountChangeCallback<Array<types.AggregatorHistoryRow>>,
|
||||
commitment: Commitment = 'confirmed'
|
||||
): number {
|
||||
return this.program.connection.onAccountChange(
|
||||
this.publicKey,
|
||||
accountInfo => {
|
||||
callback(this.decode(accountInfo.data));
|
||||
},
|
||||
commitment
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a history buffer for an aggregator and store the last N samples in a round robin history buffer.
|
||||
* @param program The SwitchboardProgram.
|
||||
* @param payer The account that will pay for the new account.
|
||||
* @param params history buffer configuration parameters.
|
||||
* @return {@linkcode TransactionObject} that will create the AggregatorHistoryBuffer.
|
||||
*
|
||||
* Basic usage example:
|
||||
*
|
||||
* ```ts
|
||||
* import {AggregatorAccount,AggregatorHistoryBuffer} from '@switchboard-xyz/solana.js';
|
||||
* const aggregatorAccount = new AggregatorAccount(program, aggregatorKey);
|
||||
* const aggregator = await aggregatorAccount.loadData();
|
||||
* const [addHistoryTxn, historyBuffer] = await AggregatorHistoryBuffer.createInstructions(program, payer, {
|
||||
* aggregatorAccount,
|
||||
* maxSamples: 10000,
|
||||
* });
|
||||
* const aggregatorHistorySignature = await program.signAndSendAll(aggregatorHistoryTxn);
|
||||
* const history = await historyBuffer.loadData();
|
||||
* ```
|
||||
*/
|
||||
public static async createInstructions(
|
||||
program: SwitchboardProgram,
|
||||
payer: PublicKey,
|
||||
params: AggregatorHistoryInit
|
||||
): Promise<[TransactionObject, AggregatorHistoryBuffer]> {
|
||||
const buffer = params.keypair ?? Keypair.generate();
|
||||
|
||||
const ixns: TransactionInstruction[] = [];
|
||||
const signers: Keypair[] = params.aggregatorAuthority
|
||||
? [params.aggregatorAuthority, buffer]
|
||||
: [buffer];
|
||||
|
||||
const size = AggregatorHistoryBuffer.getHistoryBufferSize(
|
||||
params.maxSamples
|
||||
);
|
||||
|
||||
ixns.push(
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: payer,
|
||||
newAccountPubkey: buffer.publicKey,
|
||||
space: size,
|
||||
lamports: await program.connection.getMinimumBalanceForRentExemption(
|
||||
size
|
||||
),
|
||||
programId: program.programId,
|
||||
}),
|
||||
types.aggregatorSetHistoryBuffer(
|
||||
program,
|
||||
{ params: {} },
|
||||
{
|
||||
aggregator: params.aggregatorAccount.publicKey,
|
||||
authority: params.aggregatorAuthority
|
||||
? params.aggregatorAuthority.publicKey
|
||||
: payer,
|
||||
buffer: buffer.publicKey,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return [
|
||||
new TransactionObject(payer, ixns, signers),
|
||||
new AggregatorHistoryBuffer(program, buffer.publicKey),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a history buffer for an aggregator and store the last N samples in a round robin history buffer.
|
||||
* @param program The SwitchboardProgram.
|
||||
* @param payer The account that will pay for the new account.
|
||||
* @param params history buffer configuration parameters.
|
||||
* @return {@linkcode TransactionObject} that will create the AggregatorHistoryBuffer.
|
||||
*
|
||||
* Basic usage example:
|
||||
*
|
||||
* ```ts
|
||||
* import {AggregatorAccount,AggregatorHistoryBuffer} from '@switchboard-xyz/solana.js';
|
||||
* const aggregatorAccount = new AggregatorAccount(program, aggregatorKey);
|
||||
* const aggregator = await aggregatorAccount.loadData();
|
||||
* const [addHistorySignature, historyBuffer] = await AggregatorHistoryBuffer.create(program, {
|
||||
* aggregatorAccount,
|
||||
* maxSamples: 10000,
|
||||
* });
|
||||
* const history = await historyBuffer.loadData();
|
||||
* ```
|
||||
*/
|
||||
public static async create(
|
||||
program: SwitchboardProgram,
|
||||
params: AggregatorHistoryInit
|
||||
): Promise<[TransactionSignature, AggregatorHistoryBuffer]> {
|
||||
const [transaction, account] =
|
||||
await AggregatorHistoryBuffer.createInstructions(
|
||||
program,
|
||||
program.walletPubkey,
|
||||
params
|
||||
);
|
||||
const txnSignature = await program.signAndSend(transaction);
|
||||
return [txnSignature, account];
|
||||
}
|
||||
}
|
|
@ -25,8 +25,9 @@ import { PermissionAccount } from './permissionAccount';
|
|||
import { QueueAccount } from './queueAccount';
|
||||
|
||||
/**
|
||||
* @class BufferRelayerAccount
|
||||
* Account type holding a buffer of data sourced from the buffers sole {@linkcode JobAccount}. A buffer relayer has no consensus mechanism and relies on trusting an {@linkcode OracleAccount} to respond honestly. A buffer relayer has a max capacity of 500 bytes.
|
||||
*
|
||||
* Data: {@linkcode types.BufferRelayerAccountData}
|
||||
*/
|
||||
export class BufferRelayerAccount extends Account<types.BufferRelayerAccountData> {
|
||||
static accountName = 'BufferRelayerAccountData';
|
||||
|
|
|
@ -18,8 +18,10 @@ import { AggregatorAccount } from './aggregatorAccount';
|
|||
import { QueueAccount } from './queueAccount';
|
||||
|
||||
/**
|
||||
* @class CrankAccount
|
||||
* Account holding a priority queue of aggregators and their next available update time. This is a scheduling mechanism to ensure {@linkcode AggregatorAccount}'s are updated as close as possible to their specified update interval.
|
||||
*
|
||||
* Data: {@linkcode types.CrankAccountData}
|
||||
* Buffer: Array<{@linkcode types.CrankRow}>
|
||||
*/
|
||||
export class CrankAccount extends Account<types.CrankAccountData> {
|
||||
static accountName = 'CrankAccountData';
|
||||
|
|
|
@ -14,8 +14,9 @@ import { Account } from './account';
|
|||
import { TransactionObject } from '../transaction';
|
||||
|
||||
/**
|
||||
* @class JobAccount
|
||||
* Account type storing a list of SwitchboardTasks {@linkcode OracleJob.ITask} dictating how to source data off-chain.
|
||||
*
|
||||
* Data: {@linkcode types.JobAccountData}
|
||||
*/
|
||||
export class JobAccount extends Account<types.JobAccountData> {
|
||||
static accountName = 'JobAccountData';
|
||||
|
|
|
@ -17,8 +17,9 @@ import { TransactionObject } from '../transaction';
|
|||
import { BN } from 'bn.js';
|
||||
|
||||
/**
|
||||
* @class LeaseAccount
|
||||
* Account type representing an {@linkcode AggregatorAccount}'s pre-funded escrow used to reward {@linkcode OracleAccount}'s for responding to open round requests.
|
||||
*
|
||||
* Data: {@linkcode types.LeaseAccountData}
|
||||
*/
|
||||
export class LeaseAccount extends Account<types.LeaseAccountData> {
|
||||
static accountName = 'LeaseAccountData';
|
||||
|
|
|
@ -16,10 +16,11 @@ import * as spl from '@solana/spl-token';
|
|||
import { TransactionObject } from '../transaction';
|
||||
|
||||
/**
|
||||
* @class OracleAccount
|
||||
* Account type holding an oracle's configuration including the authority and the reward/slashing wallet along with a set of metrics tracking its reliability.
|
||||
*
|
||||
* An oracle is a server that sits between the internet and a blockchain and facilitates the flow of information and is rewarded for responding with the honest majority.
|
||||
*
|
||||
* Data: {@linkcode types.OracleAccountData}
|
||||
*/
|
||||
export class OracleAccount extends Account<types.OracleAccountData> {
|
||||
static accountName = 'OracleAccountData';
|
||||
|
|
|
@ -30,10 +30,11 @@ export type PermissionSetParams =
|
|||
};
|
||||
|
||||
/**
|
||||
* @class PermissionAccount
|
||||
* Account type dictating the level of permissions between a granter and a grantee.
|
||||
*
|
||||
* A {@linkcode QueueAccount} acts as the granter where the queue authority assigns or revokes a grantee's {@linkcode types.SwitchboardPermission}. A grantee can be one of the following: {@linkcode AggregatorAccount}, {@linkcode BufferRelayerAccount}, or {@linkcode VrfAccount}.
|
||||
*
|
||||
* Data: {@linkcode types.PermissionAccountData}
|
||||
*/
|
||||
export class PermissionAccount extends Account<types.PermissionAccountData> {
|
||||
static accountName = 'PermissionAccountData';
|
||||
|
|
|
@ -13,8 +13,9 @@ import {
|
|||
import { TransactionObject } from '../transaction';
|
||||
|
||||
/**
|
||||
* @class ProgramStateAccount
|
||||
* Account type representing Switchboard global program state.
|
||||
*
|
||||
* Data: {@linkcode types.SbState}
|
||||
*/
|
||||
export class ProgramStateAccount extends Account<types.SbState> {
|
||||
static accountName = 'SbState';
|
||||
|
|
|
@ -29,10 +29,12 @@ import { PermissionAccount } from './permissionAccount';
|
|||
import { VrfAccount, VrfInitParams } from './vrfAccount';
|
||||
|
||||
/**
|
||||
* @class QueueAccount
|
||||
* Account type representing an oracle queue's configuration along with a buffer account holding a list of oracles that are actively heartbeating.
|
||||
*
|
||||
* A QueueAccount is responsible for allocating update requests to it's round robin queue of {@linkcode OracleAccount}'s.
|
||||
*
|
||||
* Data: {@linkcode types.OracleQueueAccountData}
|
||||
* Buffer: Array<PublicKey>
|
||||
*/
|
||||
export class QueueAccount extends Account<types.OracleQueueAccountData> {
|
||||
static accountName = 'OracleQueueAccountData';
|
||||
|
|
|
@ -162,9 +162,6 @@ export function fromDecoded(obj: any): types.LanesKind {
|
|||
if ('AD' in obj) {
|
||||
return new AD();
|
||||
}
|
||||
if ('BCD' in obj) {
|
||||
return new BCD();
|
||||
}
|
||||
|
||||
throw new Error('Invalid enum object');
|
||||
}
|
||||
|
@ -186,9 +183,6 @@ export function fromJSON(obj: types.LanesJSON): types.LanesKind {
|
|||
case 'AD': {
|
||||
return new AD();
|
||||
}
|
||||
case 'BCD': {
|
||||
return new BCD();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -324,26 +324,13 @@ export { Error };
|
|||
* having to know details about the data layout of the
|
||||
* `FieldElement2625x4`.
|
||||
*/
|
||||
export type LanesKind =
|
||||
| Lanes.C
|
||||
| Lanes.D
|
||||
| Lanes.AB
|
||||
| Lanes.AC
|
||||
| Lanes.CD
|
||||
| Lanes.AD
|
||||
| Lanes.BC
|
||||
| Lanes.ABCD;
|
||||
export type LanesKind = Lanes.C | Lanes.D | Lanes.AB | Lanes.AC | Lanes.AD;
|
||||
export type LanesJSON =
|
||||
| Lanes.CJSON
|
||||
| Lanes.DJSON
|
||||
| Lanes.ABJSON
|
||||
| Lanes.ACJSON
|
||||
| Lanes.CDJSON
|
||||
| Lanes.ADJSON
|
||||
| Lanes.BCJSON
|
||||
| Lanes.ABCDJSON;
|
||||
|
||||
export { Shuffle };
|
||||
| Lanes.ADJSON;
|
||||
|
||||
/**
|
||||
* The `Shuffle` enum represents a shuffle of a `FieldElement2625x4`.
|
||||
|
|
Loading…
Reference in New Issue