diff --git a/.changeset/wise-cheetahs-add.md b/.changeset/wise-cheetahs-add.md new file mode 100644 index 0000000000..1a93a30180 --- /dev/null +++ b/.changeset/wise-cheetahs-add.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/cli": minor +--- + +Transactions sent via deploy will now be retried a few times before giving up. This hopefully helps with large deploys on some chains. diff --git a/packages/cli/package.json b/packages/cli/package.json index cf9402e198..58405158ef 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -55,6 +55,7 @@ "glob": "^8.0.3", "nice-grpc-web": "^2.0.1", "openurl": "^1.1.1", + "p-retry": "^5.1.2", "path": "^0.12.7", "rxjs": "7.5.5", "throttle-debounce": "^5.0.0", diff --git a/packages/cli/src/deploy/deploy.ts b/packages/cli/src/deploy/deploy.ts index 6fa494dfd6..54b1085ead 100644 --- a/packages/cli/src/deploy/deploy.ts +++ b/packages/cli/src/deploy/deploy.ts @@ -4,7 +4,7 @@ import { deployWorld } from "./deployWorld"; import { ensureTables } from "./ensureTables"; import { Config, ConfigInput, WorldDeploy, supportedStoreVersions, supportedWorldVersions } from "./common"; import { ensureSystems } from "./ensureSystems"; -import { getTransactionReceipt, waitForTransactionReceipt } from "viem/actions"; +import { waitForTransactionReceipt } from "viem/actions"; import { getWorldDeploy } from "./getWorldDeploy"; import { ensureFunctions } from "./ensureFunctions"; import { ensureModules } from "./ensureModules"; diff --git a/packages/cli/src/deploy/ensureContract.ts b/packages/cli/src/deploy/ensureContract.ts index eaebbcf69d..959c9b663b 100644 --- a/packages/cli/src/deploy/ensureContract.ts +++ b/packages/cli/src/deploy/ensureContract.ts @@ -4,6 +4,8 @@ import { deployer } from "./ensureDeployer"; import { salt } from "./common"; import { sendTransaction } from "@latticexyz/common"; import { debug } from "./debug"; +import pRetry from "p-retry"; +import { wait } from "@latticexyz/common/utils"; export async function ensureContract({ client, @@ -24,10 +26,21 @@ export async function ensureContract({ debug("deploying", label, "at", address); return [ - await sendTransaction(client, { - chain: client.chain ?? null, - to: deployer, - data: concatHex([salt, bytecode]), - }), + await pRetry( + () => + sendTransaction(client, { + chain: client.chain ?? null, + to: deployer, + data: concatHex([salt, bytecode]), + }), + { + retries: 3, + onFailedAttempt: async (error) => { + const delay = error.attemptNumber * 500; + debug(`failed to deploy ${label}, retrying in ${delay}ms...`); + await wait(delay); + }, + } + ), ]; } diff --git a/packages/cli/src/deploy/ensureFunctions.ts b/packages/cli/src/deploy/ensureFunctions.ts index 48ffcb7606..7432697354 100644 --- a/packages/cli/src/deploy/ensureFunctions.ts +++ b/packages/cli/src/deploy/ensureFunctions.ts @@ -3,6 +3,8 @@ import { hexToResource, writeContract } from "@latticexyz/common"; import { WorldDeploy, WorldFunction, worldAbi } from "./common"; import { debug } from "./debug"; import { getFunctions } from "./getFunctions"; +import pRetry from "p-retry"; +import { wait } from "@latticexyz/common/utils"; export async function ensureFunctions({ client, @@ -40,23 +42,45 @@ export async function ensureFunctions({ toAdd.map((func) => { const { namespace } = hexToResource(func.systemId); if (namespace === "") { - return writeContract(client, { - chain: client.chain ?? null, - address: worldDeploy.address, - abi: worldAbi, - // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) - functionName: "registerRootFunctionSelector", - args: [func.systemId, func.systemFunctionSignature, func.systemFunctionSelector], - }); + return pRetry( + () => + writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: worldAbi, + // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) + functionName: "registerRootFunctionSelector", + args: [func.systemId, func.systemFunctionSignature, func.systemFunctionSelector], + }), + { + retries: 3, + onFailedAttempt: async (error) => { + const delay = error.attemptNumber * 500; + debug(`failed to register function ${func.signature}, retrying in ${delay}ms...`); + await wait(delay); + }, + } + ); } - return writeContract(client, { - chain: client.chain ?? null, - address: worldDeploy.address, - abi: worldAbi, - // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) - functionName: "registerFunctionSelector", - args: [func.systemId, func.systemFunctionSignature], - }); + return pRetry( + () => + writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: worldAbi, + // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) + functionName: "registerFunctionSelector", + args: [func.systemId, func.systemFunctionSignature], + }), + { + retries: 3, + onFailedAttempt: async (error) => { + const delay = error.attemptNumber * 500; + debug(`failed to register function ${func.signature}, retrying in ${delay}ms...`); + await wait(delay); + }, + } + ); }) ); } diff --git a/packages/cli/src/deploy/ensureModules.ts b/packages/cli/src/deploy/ensureModules.ts index 9efc1ac3b3..7d7e992567 100644 --- a/packages/cli/src/deploy/ensureModules.ts +++ b/packages/cli/src/deploy/ensureModules.ts @@ -3,7 +3,8 @@ import { writeContract } from "@latticexyz/common"; import { Module, WorldDeploy, worldAbi } from "./common"; import { ensureContract } from "./ensureContract"; import { debug } from "./debug"; -import { uniqueBy } from "@latticexyz/common/utils"; +import { uniqueBy, wait } from "@latticexyz/common/utils"; +import pRetry from "p-retry"; export async function ensureModules({ client, @@ -28,22 +29,44 @@ export async function ensureModules({ const installTxs = await Promise.all( modules.map((mod) => mod.installAsRoot - ? writeContract(client, { - chain: client.chain ?? null, - address: worldDeploy.address, - abi: worldAbi, - // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) - functionName: "installRootModule", - args: [mod.address, mod.installData], - }) - : writeContract(client, { - chain: client.chain ?? null, - address: worldDeploy.address, - abi: worldAbi, - // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) - functionName: "installModule", - args: [mod.address, mod.installData], - }) + ? pRetry( + () => + writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: worldAbi, + // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) + functionName: "installRootModule", + args: [mod.address, mod.installData], + }), + { + retries: 3, + onFailedAttempt: async (error) => { + const delay = error.attemptNumber * 500; + debug(`failed to install root module ${mod.name}, retrying in ${delay}ms...`); + await wait(delay); + }, + } + ) + : pRetry( + () => + writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: worldAbi, + // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) + functionName: "installModule", + args: [mod.address, mod.installData], + }), + { + retries: 3, + onFailedAttempt: async (error) => { + const delay = error.attemptNumber * 500; + debug(`failed to install module ${mod.name}, retrying in ${delay}ms...`); + await wait(delay); + }, + } + ) ) ); diff --git a/packages/cli/src/deploy/ensureSystems.ts b/packages/cli/src/deploy/ensureSystems.ts index 2a40d81d92..b7dfa6db4d 100644 --- a/packages/cli/src/deploy/ensureSystems.ts +++ b/packages/cli/src/deploy/ensureSystems.ts @@ -6,7 +6,8 @@ import { debug } from "./debug"; import { resourceLabel } from "./resourceLabel"; import { getSystems } from "./getSystems"; import { getResourceAccess } from "./getResourceAccess"; -import { uniqueBy } from "@latticexyz/common/utils"; +import { uniqueBy, wait } from "@latticexyz/common/utils"; +import pRetry from "p-retry"; export async function ensureSystems({ client, @@ -54,22 +55,44 @@ export async function ensureSystems({ const accessTxs = await Promise.all([ ...accessToRemove.map((access) => - writeContract(client, { - chain: client.chain ?? null, - address: worldDeploy.address, - abi: worldAbi, - functionName: "revokeAccess", - args: [access.resourceId, access.address], - }) + pRetry( + () => + writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: worldAbi, + functionName: "revokeAccess", + args: [access.resourceId, access.address], + }), + { + retries: 3, + onFailedAttempt: async (error) => { + const delay = error.attemptNumber * 500; + debug(`failed to revoke access, retrying in ${delay}ms...`); + await wait(delay); + }, + } + ) ), ...accessToAdd.map((access) => - writeContract(client, { - chain: client.chain ?? null, - address: worldDeploy.address, - abi: worldAbi, - functionName: "grantAccess", - args: [access.resourceId, access.address], - }) + pRetry( + () => + writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: worldAbi, + functionName: "grantAccess", + args: [access.resourceId, access.address], + }), + { + retries: 3, + onFailedAttempt: async (error) => { + const delay = error.attemptNumber * 500; + debug(`failed to grant access, retrying in ${delay}ms...`); + await wait(delay); + }, + } + ) ), ]); @@ -114,14 +137,25 @@ export async function ensureSystems({ // then start registering systems const registerTxs = await Promise.all( missingSystems.map((system) => - writeContract(client, { - chain: client.chain ?? null, - address: worldDeploy.address, - abi: worldAbi, - // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) - functionName: "registerSystem", - args: [system.systemId, system.address, system.allowAll], - }) + pRetry( + () => + writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: worldAbi, + // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) + functionName: "registerSystem", + args: [system.systemId, system.address, system.allowAll], + }), + { + retries: 3, + onFailedAttempt: async (error) => { + const delay = error.attemptNumber * 500; + debug(`failed to register system ${resourceLabel(system)}, retrying in ${delay}ms...`); + await wait(delay); + }, + } + ) ) ); diff --git a/packages/cli/src/deploy/ensureTables.ts b/packages/cli/src/deploy/ensureTables.ts index 9834e3f6f4..cd31975bda 100644 --- a/packages/cli/src/deploy/ensureTables.ts +++ b/packages/cli/src/deploy/ensureTables.ts @@ -6,6 +6,8 @@ import { valueSchemaToFieldLayoutHex, keySchemaToHex, valueSchemaToHex } from "@ import { debug } from "./debug"; import { resourceLabel } from "./resourceLabel"; import { getTables } from "./getTables"; +import pRetry from "p-retry"; +import { wait } from "@latticexyz/common/utils"; export async function ensureTables({ client, @@ -29,21 +31,32 @@ export async function ensureTables({ debug("registering tables", missingTables.map(resourceLabel).join(", ")); return await Promise.all( missingTables.map((table) => - writeContract(client, { - chain: client.chain ?? null, - address: worldDeploy.address, - abi: worldAbi, - // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) - functionName: "registerTable", - args: [ - table.tableId, - valueSchemaToFieldLayoutHex(table.valueSchema), - keySchemaToHex(table.keySchema), - valueSchemaToHex(table.valueSchema), - Object.keys(table.keySchema), - Object.keys(table.valueSchema), - ], - }) + pRetry( + () => + writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: worldAbi, + // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) + functionName: "registerTable", + args: [ + table.tableId, + valueSchemaToFieldLayoutHex(table.valueSchema), + keySchemaToHex(table.keySchema), + valueSchemaToHex(table.valueSchema), + Object.keys(table.keySchema), + Object.keys(table.valueSchema), + ], + }), + { + retries: 3, + onFailedAttempt: async (error) => { + const delay = error.attemptNumber * 500; + debug(`failed to register table ${resourceLabel(table)}, retrying in ${delay}ms...`); + await wait(delay); + }, + } + ) ) ); } diff --git a/packages/cli/src/runDeploy.ts b/packages/cli/src/runDeploy.ts index 0a5f370a70..a7466ddf81 100644 --- a/packages/cli/src/runDeploy.ts +++ b/packages/cli/src/runDeploy.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { InferredOptionTypes, Options } from "yargs"; import { deploy } from "./deploy/deploy"; import { createWalletClient, http, Hex } from "viem"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e9c734ee4..8942de00f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,6 +193,9 @@ importers: openurl: specifier: ^1.1.1 version: 1.1.1 + p-retry: + specifier: ^5.1.2 + version: 5.1.2 path: specifier: ^0.12.7 version: 0.12.7