Skip to content

Commit

Permalink
Split up provider/providerMut (#223)
Browse files Browse the repository at this point in the history
* Adds method to getAccountInfo of a Provider.

* Split up read operations from provider write operations

* more readonlyprovider stuff

* readonly provider
  • Loading branch information
macalinao committed Aug 31, 2021
1 parent 88c485b commit a73def0
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 85 deletions.
36 changes: 30 additions & 6 deletions packages/solana-contrib/src/interfaces.ts
@@ -1,6 +1,7 @@
import type {
ConfirmOptions,
Connection,
KeyedAccountInfo,
PublicKey,
RpcResponseAndContext,
Signer,
Expand All @@ -20,18 +21,41 @@ export interface Wallet {
publicKey: PublicKey;
}

export type SendTxRequest = {
/**
* Request to send a transaction.
*/
export interface SendTxRequest {
tx: Transaction;
signers: Array<Signer | undefined>;
};
}

/**
* An entity that can fetch {@link KeyedAccountInfo}.
*/
export interface AccountInfoFetcher {
/**
* Fetches the {@link KeyedAccountInfo} associated with a
* {@link PublicKey}, if it exists.
*
* @param accountId The account
*/
getAccountInfo(accountId: PublicKey): Promise<KeyedAccountInfo | null>;
}

export interface ReadonlyProvider extends AccountInfoFetcher {
/**
* Connection for reading data.
*/
connection: Connection;
}

/**
* The network and wallet context used to send transactions paid for and signed
* by the provider.
*
* This interface is adapted from Anchor.
* This interface is based on Anchor, but includes more features.
*/
export interface Provider {
export interface Provider extends ReadonlyProvider {
/**
* Connection for reading data.
*/
Expand Down Expand Up @@ -92,10 +116,10 @@ export interface Provider {
/**
* An event emitted by a program.
*/
export type Event = {
export interface Event {
name: string;
data: Record<string, unknown>;
};
}

/**
* Parses the events from logs.
Expand Down
104 changes: 46 additions & 58 deletions packages/solana-contrib/src/provider.ts
@@ -1,7 +1,8 @@
import type {
Commitment,
ConfirmOptions,
Connection,
KeyedAccountInfo,
PublicKey,
RpcResponseAndContext,
Signer,
SimulatedTransactionResponse,
Expand All @@ -10,21 +11,57 @@ import type {
} from "@solana/web3.js";
import { sendAndConfirmRawTransaction } from "@solana/web3.js";

import type { ReadonlyProvider } from ".";
import type { Provider, SendTxRequest, Wallet } from "./interfaces";
import { sendAll } from "./utils";
import { sendAll, simulateTransactionWithCommitment } from "./utils";

export const DEFAULT_PROVIDER_OPTIONS: ConfirmOptions = {
preflightCommitment: "recent",
commitment: "recent",
};

/**
* Provider that can only read.
*/
export class SolanaReadonlyProvider implements ReadonlyProvider {
/**
* @param connection The cluster connection where the program is deployed.
* @param sendConnection The connection where transactions are sent to.
* @param wallet The wallet used to pay for and sign all transactions.
* @param opts Transaction confirmation options to use by default.
*/
constructor(
public readonly connection: Connection,
public readonly opts: ConfirmOptions = DEFAULT_PROVIDER_OPTIONS
) {}

/**
* Gets
* @param accountId
* @returns
*/
async getAccountInfo(accountId: PublicKey): Promise<KeyedAccountInfo | null> {
const accountInfo = await this.connection.getAccountInfo(
accountId,
this.opts.commitment
);
if (!accountInfo) {
return null;
}
return {
accountId,
accountInfo,
};
}
}

/**
* The network and wallet context used to send transactions paid for and signed
* by the provider.
*
* This implementation was taken from Anchor.
*/
export class SolanaProvider implements Provider {
export class SolanaProvider extends SolanaReadonlyProvider implements Provider {
/**
* @param connection The cluster connection where the program is deployed.
* @param sendConnection The connection where transactions are sent to.
Expand All @@ -36,10 +73,8 @@ export class SolanaProvider implements Provider {
public readonly sendConnection: Connection,
public readonly wallet: Wallet,
public readonly opts: ConfirmOptions = DEFAULT_PROVIDER_OPTIONS
) {}

static defaultOptions(): ConfirmOptions {
return DEFAULT_PROVIDER_OPTIONS;
) {
super(connection, opts);
}

/**
Expand Down Expand Up @@ -89,9 +124,9 @@ export class SolanaProvider implements Provider {
* Similar to `send`, but for an array of transactions and signers.
*/
async sendAll(
reqs: Array<SendTxRequest>,
reqs: SendTxRequest[],
opts?: ConfirmOptions
): Promise<Array<TransactionSignature>> {
): Promise<TransactionSignature[]> {
return await sendAll({
provider: this,
reqs,
Expand Down Expand Up @@ -134,57 +169,10 @@ export class SolanaProvider implements Provider {
tx.partialSign(kp);
});

return await simulateTransaction(
return await simulateTransactionWithCommitment(
this.connection,
tx,
opts.commitment ?? this.opts.commitment ?? "recent"
opts.commitment ?? this.opts.commitment
);
}
}

// Copy of Connection.simulateTransaction that takes a commitment parameter.
export async function simulateTransaction(
connection: Connection,
transaction: Transaction,
commitment: Commitment
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
const connectionInner = connection as Connection & {
_disableBlockhashCaching: boolean;
_recentBlockhash: (disableBlockhashCaching: boolean) => Promise<string>;
_rpcRequest: (
rpc: "simulateTransaction",
args: [
string,
{
encoding: string;
commitment: Commitment;
}
]
) => Promise<{
error: Error;
result: RpcResponseAndContext<SimulatedTransactionResponse>;
}>;
};
const transactionTyped = transaction as Transaction & {
_serialize: (buffer: Buffer) => Buffer;
};

transaction.recentBlockhash = await connectionInner._recentBlockhash(
connectionInner._disableBlockhashCaching
);

const signData = transaction.serializeMessage();

const wireTransaction = transactionTyped._serialize(signData);
const encodedTransaction = wireTransaction.toString("base64");
const config = { encoding: "base64", commitment };

const res = await connectionInner._rpcRequest("simulateTransaction", [
encodedTransaction,
config,
]);
if (res.error) {
throw new Error("failed to simulate transaction: " + res.error.message);
}
return res.result;
}
6 changes: 3 additions & 3 deletions packages/solana-contrib/src/transaction/PendingTransaction.ts
Expand Up @@ -2,7 +2,7 @@ import type { Finality, TransactionSignature } from "@solana/web3.js";
import promiseRetry from "promise-retry";
import type { OperationOptions } from "retry";

import type { Provider } from "../interfaces";
import type { ReadonlyProvider } from "../interfaces";
import { TransactionReceipt } from "../transaction";

/**
Expand All @@ -12,7 +12,7 @@ export class PendingTransaction {
receipt: TransactionReceipt | null = null;

constructor(
public readonly provider: Provider,
public readonly provider: ReadonlyProvider,
public readonly signature: TransactionSignature
) {}

Expand All @@ -35,7 +35,7 @@ export class PendingTransaction {
}
const receipt = await promiseRetry(
async (retry) => {
const result = await this.provider.sendConnection.getTransaction(
const result = await this.provider.connection.getTransaction(
this.signature,
{
commitment,
Expand Down
25 changes: 25 additions & 0 deletions packages/solana-contrib/src/transaction/TransactionEnvelope.ts
Expand Up @@ -112,6 +112,31 @@ export class TransactionEnvelope {
}));
}

/**
* Returns a string representation of the {@link TransactionEnvelope}.
*/
get debugStr(): string {
return [
"=> Instructions",
this.instructions
.map((ser, i) => {
return [
`Instruction ${i}: ${ser.programId.toString()}`,
...ser.keys.map(
(k, i) =>
` [${i}] ${k.pubkey.toString()} ${
k.isWritable ? "(mut)" : ""
} ${k.isSigner ? "(signer)" : ""}`
),
` Data (base64): ${ser.data.toString("base64")}`,
].join("\n");
})
.join("\n"),
"=> Signers",
this.signers.map((sg) => sg.publicKey.toString()).join("\n"),
].join("\n");
}

/**
* Combines multiple TransactionEnvelopes into one.
*/
Expand Down
11 changes: 7 additions & 4 deletions packages/solana-contrib/src/transaction/TransactionReceipt.ts
@@ -1,7 +1,10 @@
import type { TransactionResponse } from "@solana/web3.js";
import type {
TransactionResponse,
TransactionSignature,
} from "@solana/web3.js";
import invariant from "tiny-invariant";

import type { Event, EventParser, Provider } from "../interfaces";
import type { Event, EventParser, ReadonlyProvider } from "../interfaces";

/**
* A transaction that has been processed by the cluster.
Expand All @@ -11,11 +14,11 @@ export class TransactionReceipt {
/**
* Current provider.
*/
public readonly provider: Provider,
public readonly provider: ReadonlyProvider,
/**
* Signature (id) of the transaction.
*/
public readonly signature: string,
public readonly signature: TransactionSignature,
/**
* Raw response from web3.js
*/
Expand Down
1 change: 1 addition & 0 deletions packages/solana-contrib/src/utils/index.ts
@@ -1,2 +1,3 @@
export * from "./isPublicKey";
export * from "./sendAll";
export * from "./simulateTransaction";
56 changes: 56 additions & 0 deletions packages/solana-contrib/src/utils/simulateTransaction.ts
@@ -0,0 +1,56 @@
import type {
Commitment,
Connection,
RpcResponseAndContext,
SimulatedTransactionResponse,
Transaction,
} from "@solana/web3.js";

/**
* Copy of Connection.simulateTransaction that takes a commitment parameter.
*/
export async function simulateTransactionWithCommitment(
connection: Connection,
transaction: Transaction,
commitment: Commitment = "recent"
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
const connectionInner = connection as Connection & {
_disableBlockhashCaching: boolean;
_recentBlockhash: (disableBlockhashCaching: boolean) => Promise<string>;
_rpcRequest: (
rpc: "simulateTransaction",
args: [
string,
{
encoding: string;
commitment: Commitment;
}
]
) => Promise<{
error: Error;
result: RpcResponseAndContext<SimulatedTransactionResponse>;
}>;
};
const transactionTyped = transaction as Transaction & {
_serialize: (buffer: Buffer) => Buffer;
};

transaction.recentBlockhash = await connectionInner._recentBlockhash(
connectionInner._disableBlockhashCaching
);

const signData = transaction.serializeMessage();

const wireTransaction = transactionTyped._serialize(signData);
const encodedTransaction = wireTransaction.toString("base64");
const config = { encoding: "base64", commitment };

const res = await connectionInner._rpcRequest("simulateTransaction", [
encodedTransaction,
config,
]);
if (res.error) {
throw new Error("failed to simulate transaction: " + res.error.message);
}
return res.result;
}

0 comments on commit a73def0

Please sign in to comment.