Skip to content

Commit

Permalink
CLI: improve Solana transaction confirmations (#281)
Browse files Browse the repository at this point in the history
* send and confirm tx via metaplex; retry only transient errors

* do not disable retry on rate limit when sending tx

* update solana web3.js

* patch solana/web3.js to increase tx confirmation sleep time

* remove sending tx log
  • Loading branch information
konoart authored Jun 7, 2024
1 parent 3c61c8b commit e585d7e
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 108 deletions.
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,10 @@
"prettier": "^2.7.1",
"shx": "^0.3.4",
"typescript": "^4.7.4"
},
"pnpm": {
"patchedDependencies": {
"@solana/[email protected]": "patches/@[email protected]"
}
}
}
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"@aws-sdk/client-s3": "^3.321.1",
"@metaplex-foundation/js-plugin-aws": "^0.20.0",
"@solana-mobile/dapp-store-publishing-tools": "workspace:0.9.1",
"@solana/web3.js": "1.68.0",
"@solana/web3.js": "1.92.1",
"@types/semver": "^7.3.13",
"ajv": "^8.11.0",
"boxen": "^7.0.1",
Expand Down
36 changes: 7 additions & 29 deletions packages/cli/src/commands/create/CreateCliApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import {
Connection,
Keypair,
PublicKey,
sendAndConfirmTransaction,
} from "@solana/web3.js";

import {
Constants,
getMetaplexInstance,
showMessage,
} from "../../CliUtils.js";
import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
import { sendAndConfirmTransaction } from "../utils.js";

const createAppNft = async (
{
Expand Down Expand Up @@ -46,33 +45,13 @@ const createAppNft = async (
);

console.info(`App NFT data upload complete\nSigning transaction now`);
const maxTries = 8;
for (let i = 1; i <= maxTries; i++) {
try {
const blockhash = await connection.getLatestBlockhashAndContext();
const tx = txBuilder.toTransaction(blockhash.value);
tx.sign(mintAddress, publisher);

const txSig = await sendAndConfirmTransaction(connection, tx, [
publisher,
mintAddress,
], {
commitment: "confirmed",
minContextSlot: blockhash.context.slot
});
return { appAddress: mintAddress.publicKey.toBase58(), transactionSignature: txSig };
} catch (e) {
const errorMsg = (e as Error | null)?.message ?? "";
if (i == maxTries) {
showMessage("Transaction Failure", errorMsg, "error");
process.exit(-1)
} else {
const retryMsg = errorMsg + "\nWill Retry minting app NFT."
showMessage("Transaction Failure", retryMsg, "standard");
}
}
}
throw new Error("Unable to mint app NFT");
const { response } = await sendAndConfirmTransaction(metaplex, txBuilder);

return {
appAddress: mintAddress.publicKey.toBase58(),
transactionSignature: response.signature,
};
};

type CreateAppCommandInput = {
Expand All @@ -96,7 +75,6 @@ export const createAppCommand = async ({
url,
{
commitment: "confirmed",
disableRetryOnRateLimit: true,
}
);

Expand Down
36 changes: 7 additions & 29 deletions packages/cli/src/commands/create/CreateCliPublisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ import { createPublisher } from "@solana-mobile/dapp-store-publishing-tools";
import {
Connection,
Keypair,
sendAndConfirmTransaction,
} from "@solana/web3.js";

import {
Constants,
getMetaplexInstance,
showMessage,
} from "../../CliUtils.js";
import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
import { sendAndConfirmTransaction } from "../utils.js";

const createPublisherNft = async (
{
Expand All @@ -37,33 +36,13 @@ const createPublisherNft = async (
);

console.info(`Publisher NFT data upload complete\nSigning transaction now`);
const maxTries = 8;
for (let i = 1; i <= maxTries; i++) {
try {
const blockhash = await connection.getLatestBlockhashAndContext();
const tx = txBuilder.toTransaction(blockhash.value);
tx.sign(mintAddress, publisher);

const txSig = await sendAndConfirmTransaction(connection, tx, [
publisher,
mintAddress,
], {
commitment: "confirmed",
minContextSlot: blockhash.context.slot
});
return { publisherAddress: mintAddress.publicKey.toBase58(), transactionSignature: txSig};
} catch (e) {
const errorMsg = (e as Error | null)?.message ?? "";
if (i == maxTries) {
showMessage("Transaction Failure", errorMsg, "error");
process.exit(-1)
} else {
const retryMsg = errorMsg + "\nWill Retry minting publisher."
showMessage("Transaction Failure", retryMsg, "standard");
}
}
}
throw new Error("Unable to mint publisher NFT");
const { response } = await sendAndConfirmTransaction(metaplex, txBuilder);

return {
publisherAddress: mintAddress.publicKey.toBase58(),
transactionSignature: response.signature,
};
};

export const createPublisherCommand = async ({
Expand All @@ -83,7 +62,6 @@ export const createPublisherCommand = async ({
url,
{
commitment: "confirmed",
disableRetryOnRateLimit: true,
}
);

Expand Down
37 changes: 8 additions & 29 deletions packages/cli/src/commands/create/CreateCliRelease.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ import {
Connection,
Keypair,
PublicKey,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import fs from "fs";
import { createHash } from "crypto";
import {
Constants,
getMetaplexInstance,
showMessage
} from "../../CliUtils.js";
import { PublishDetails, loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
import { sendAndConfirmTransaction } from "../utils.js";

type CreateReleaseCommandInput = {
appMintAddress: string;
Expand Down Expand Up @@ -67,32 +66,13 @@ const createReleaseNft = async ({
);

console.info(`Release NFT data upload complete\nSigning transaction now`);
const maxTries = 8;
for (let i = 1; i <= maxTries; i++) {
try {
const blockhash = await connection.getLatestBlockhashAndContext();
const tx = txBuilder.toTransaction(blockhash.value);
tx.sign(releaseMintAddress, publisher);
const txSig = await sendAndConfirmTransaction(connection, tx, [
publisher,
releaseMintAddress,
], {
commitment: "confirmed",
minContextSlot: blockhash.context.slot,
});
return { releaseAddress: releaseMintAddress.publicKey.toBase58(), transactionSignature: txSig };
} catch (e) {
const errorMsg = (e as Error | null)?.message ?? "";
if (i == maxTries) {
showMessage("Transaction Failure", errorMsg, "error");
process.exit(-1)
} else {
const retryMsg = errorMsg + "\nWill Retry minting release NFT"
showMessage("Transaction Failure", retryMsg, "standard");
}
}
}
throw new Error("Unable to mint release NFT");

const { response } = await sendAndConfirmTransaction(metaplex, txBuilder);

return {
releaseAddress: releaseMintAddress.publicKey.toBase58(),
transactionSignature: response.signature,
};
};

export const createReleaseCommand = async ({
Expand All @@ -108,7 +88,6 @@ export const createReleaseCommand = async ({
url,
{
commitment: "confirmed",
disableRetryOnRateLimit: true,
}
);

Expand Down
33 changes: 33 additions & 0 deletions packages/cli/src/commands/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
type Metaplex,
type TransactionBuilder,
FailedToConfirmTransactionError,
} from "@metaplex-foundation/js";
import { TransactionExpiredBlockheightExceededError } from "@solana/web3.js";

export async function sendAndConfirmTransaction(
metaplex: Metaplex,
builder: TransactionBuilder
): ReturnType<TransactionBuilder["sendAndConfirm"]> {
for (let i = 0; i < 10; i++) {
try {
return await builder.sendAndConfirm(metaplex);
} catch (e: unknown) {
if (isTransientError(e)) {
continue;
}

throw e;
}
}

throw new Error("Unable to send transaction. Please try later.");
}

function isTransientError(e: unknown): boolean {
return (
e instanceof FailedToConfirmTransactionError &&
(e.cause instanceof TransactionExpiredBlockheightExceededError ||
/blockhash not found/i.test(e.cause?.message ?? ""))
);
}
13 changes: 13 additions & 0 deletions patches/@[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/lib/index.cjs.js b/lib/index.cjs.js
index 013925eb21352fb560e87229ae33de94ff83115a..42ca54b5023ee9731bf8f35cb3cb49276f79dfba 100644
--- a/lib/index.cjs.js
+++ b/lib/index.cjs.js
@@ -6587,7 +6587,7 @@ class Connection {
let currentBlockHeight = await checkBlockHeight();
if (done) return;
while (currentBlockHeight <= lastValidBlockHeight) {
- await sleep(1000);
+ await sleep(5000);
if (done) return;
currentBlockHeight = await checkBlockHeight();
if (done) return;
Loading

0 comments on commit e585d7e

Please sign in to comment.