Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split up provider/providerMut #223

Merged
merged 5 commits into from Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
}