Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
bdf7826
rollback dep of zkvm-prover
noel2004 Nov 25, 2025
9839bf7
prover quit while proving process has panicked
noel2004 Nov 25, 2025
d262a63
Merge remote-tracking branch 'origin/develop' into feat/prover_4.7
noel2004 Nov 25, 2025
dc29c8c
Merge remote-tracking branch 'origin/develop' into feat/prover_4.7
noel2004 Nov 25, 2025
af63bc0
fix wrong configuration
noel2004 Nov 25, 2025
5c2803c
Merge remote-tracking branch 'origin/develop' into feat/prover_4.7
noel2004 Dec 3, 2025
5ae31bc
Merge remote-tracking branch 'origin/develop' into feat/prover_4.7
noel2004 Dec 3, 2025
98be0a0
Merge remote-tracking branch 'origin/develop' into feat/prover_4.7
noel2004 Dec 9, 2025
b244fa8
Merge remote-tracking branch 'origin/develop' into feat/prover_4.7
noel2004 Dec 16, 2025
e852915
update zkvm-prover
noel2004 Dec 16, 2025
b833468
add debug mode, trivial fixings
noel2004 Dec 16, 2025
930a12a
update zkvm-prover dep and openvm to 1.4.2
noel2004 Dec 16, 2025
3b174f8
add test data for mainnet galileo and prune feynman (can not be teste…
noel2004 Dec 16, 2025
74a3d7a
udpate zkvm-prover dep
noel2004 Dec 17, 2025
ede29c7
init dumper
noel2004 Dec 18, 2025
b270d96
dumper
noel2004 Dec 18, 2025
79d79ed
fix
noel2004 Dec 18, 2025
d306b38
fix
noel2004 Dec 18, 2025
03b992f
json mode
noel2004 Dec 18, 2025
492563f
Merge remote-tracking branch 'origin/develop' into feat/zkvm_prover_142
noel2004 Jan 16, 2026
6a57a2e
update dep of zkvm prover and trivial fixing
noel2004 Jan 16, 2026
8955d4f
Merge branch 'feat/zkvm_prover_143' into develop
noel2004 Feb 16, 2026
58297d3
fix e2e tool
noel2004 Feb 19, 2026
b76539b
update test env
noel2004 Feb 19, 2026
93f755c
update dep
noel2004 Feb 20, 2026
252eea9
use galileov2 for cloak test enviroment
noel2004 Feb 20, 2026
a35834d
fix make file in e2e test
noel2004 Feb 20, 2026
e1aa90b
update ZKVM_VERSION
noel2004 Feb 25, 2026
f2a9ba5
upgrade dep
noel2004 Mar 2, 2026
649c441
pump crates version
noel2004 Mar 2, 2026
09d83e1
udpate testing configuration
noel2004 Mar 6, 2026
fafcea4
Merge remote-tracking branch 'origin/develop' into feat/zkvm_prover_143
noel2004 Mar 13, 2026
bbe6c41
update proving-sdk
noel2004 Mar 18, 2026
766aa25
update zkvm-prover with patched openvm 1.4.3
noel2004 Mar 23, 2026
b4ccd70
update zkvm-prover and test stuff
noel2004 Mar 24, 2026
1408430
AI helper: init
noel2004 Feb 25, 2026
343ed78
refine the skill and update makefile
noel2004 Mar 24, 2026
ac40de8
agentic the local test
noel2004 Apr 13, 2026
7643561
add unit test agent
noel2004 Apr 14, 2026
949271c
fix "put into background" instruction
noel2004 Apr 14, 2026
00c407c
+ fix local memory instruction
noel2004 Apr 15, 2026
b19dac8
update ignore files
noel2004 Apr 15, 2026
b0575d9
update zkvm prover
lispc May 19, 2026
1e020b9
fix cargo check
Velaciela May 19, 2026
d595390
fix cargo fmt import ordering
Velaciela May 19, 2026
4657129
add docs
Velaciela May 19, 2026
d46829c
test e2e done
Velaciela May 19, 2026
13193e8
Merge origin/develop into feat/zkvm_prover_143
Velaciela May 19, 2026
4ac05e7
done
Velaciela May 20, 2026
37a43ba
fix ci
Velaciela May 20, 2026
7186b4a
remove coordinator-proxy in e2e test
Velaciela May 22, 2026
db1b42c
update .gitignore
lispc May 22, 2026
9918cd4
chore: bump version to v4.7.13-openvm16 for custom OpenVM 1.6 build
lispc May 22, 2026
4d0a683
Merge remote-tracking branch 'origin/develop' into feat/zkvm_prover_143
lispc May 29, 2026
b9a4a3a
docs(setup): add shadow coordinator + prover testing guide and scripts
lispc May 29, 2026
129d6e5
feat(sender): add dry-run mode for transaction simulation via eth_call
lispc May 29, 2026
9cab5b6
update docs/shadow-testing/README.md
lispc May 30, 2026
b22b4cf
docs
lispc May 31, 2026
341c038
update docs
lispc May 31, 2026
2a7d7b5
update docs
lispc Jun 1, 2026
204843b
feat(shadow-testing): add mainnet shadow fork toolkit + manual finali…
lispc Jun 2, 2026
c424e53
update docs
lispc Jun 3, 2026
ee3b61a
fix(shadow-testing): correct verifier contract, relayer bypass, and docs
lispc Jun 3, 2026
5a87b79
docs: add Sepolia shadow testing rules and agent discipline
lispc Jun 3, 2026
2422f5f
docs(shadow-testing): verifier wrapper lessons, anvil gas fix, and l2…
lispc Jun 4, 2026
02e375b
refactor(shadow-testing): merge scripts/ into tests/, deduplicate doc…
lispc Jun 4, 2026
ab3e21f
shadow-testing: fix prover stack overflow by adding RUST_MIN_STACK
lispc Jun 4, 2026
5b0f8f4
docs(shadow-testing): document L1MessageQueueV2 state sync fix for bu…
lispc Jun 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ target
zkvm-prover/*.json
.work/
rollup/tests.test
local-secrets.md
tmp/
89 changes: 89 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,45 @@ Follow the structured testing guide in [`docs/testing/openvm-upgrade-testing-gui
4. End-to-end proving
5. Docker image builds

## Shadow Coordinator + Prover Testing (Production Task Replay)

For testing proof generation against **real mainnet production tasks** without interfering with the live system, use the **Shadow Coordinator** approach. This is significantly faster than a full shadow fork:

- **Architecture**: Local coordinator (`:8390`) + local prover (GPU), fed by imported production task data.
- **Docs**: [`tests/shadow-testing/docs/GUIDE.md`](tests/shadow-testing/docs/GUIDE.md) — full setup guide, troubleshooting, config reference.
- **Quick Start**: [`tests/shadow-testing/README.md`](tests/shadow-testing/README.md)
- **Automation**: [`tests/shadow-testing/Makefile`](tests/shadow-testing/Makefile) — Makefile targets for Docker and bare-metal shadow fork testing.

Key hard-won rules:
- **L2 RPC for coordinator task generation** (must support `debug_executionWitness`):
- ✅ **Primary**: `https://l2geth-rpc-proxy.mainnet.aws.scroll.io/` (internal/debug-enabled, supports `debug_executionWitness`)
- ⚠️ **Fallback**: `https://mainnet-rpc.scroll.io` (public RPC, may not support `debug_executionWitness` for chunk task generation)
- ❌ **Avoid**: `https://rpc.scroll.io` (does not work)
- **Alchemy API for Anvil fork** (must use Alchemy, others hit rate limits):
- ✅ **Primary**: `https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY`
- 📋 **Credential source**: Check `local-secrets.md`, `.env`, or `.pgpass` first. If not found, **ask a human** — do not guess or invent keys.
- **S3 circuit URLs**: v0.8.0 uses `v0.8.0/` prefix (no `/releases/`).
- **l2_block table**: Coordinator needs this for block hash lookups. Must be populated and linked via `chunk_hash`.
- **Blocks**: Must be post-fork (GalileoV2 / codec V10 = blocks ≥ 33,750,000 on mainnet).
- **L1 messages**: If chunks contain L1 messages, prover needs `scroll_getL1MessagesInBlock` RPC support. Most chunks at current mainnet height do NOT contain L1 messages, so this is usually non-blocking.
- **Anvil MUST fork Ethereum L1, NOT Scroll L2**: The ScrollChain proxy address `0xa13BAF47339d63B743e7Da8741db5456DAc1E556` is on **Ethereum mainnet** (chainId=1), not Scroll mainnet (chainId=534352). If you accidentally point Anvil at a Scroll L2 RPC (e.g., `scroll-mainnet.g.alchemy.com`), the proxy address will have no code or wrong code, and all contract interactions will fail. Always verify `eth_chainId` returns `1` after forking.

### Sepolia Shadow Fork — Additional Rules

| Dimension | Mainnet | Sepolia | Trap |
|-----------|---------|---------|------|
| **DB port** | `localhost:5433` (shadow) / `15432` (RDS tunnel) | `localhost:25432` (RDS tunnel) | Wrong port = connecting to mainnet data |
| **L2 RPC** | `l2geth-rpc-proxy.mainnet.aws.scroll.io` | `l2geth-rpc-proxy.sepolia.aws.scroll.io` | Public Sepolia RPC (`sepolia-rpc.scroll.io`) rejects `debug_executionWitness` |
| **Verifier** | Mainnet has `latestVerifier[10] = 0x0dE1...` (can `anvil_setCode`) | Production proofs + production MVRV may already match | Re-using old proofs → check MVRV first. Testing **new guest** → MUST deploy fresh verifier |
| **`committedBatches`** | Sparse, but fork block usually covers target batches | Sparse; **every bundle end batch must exist** | Missing entry → `ErrorIncorrectBatchHash(0x2a1c1442)` |
| **`L1MessageQueueV2`** | Reset `nextUnfinalizedQueueIndex = 0` sufficient | Set to `MIN(total_l1_messages_popped_before)` of first target batch; **slot 104** (verify with `forge inspect`) | Wrong slot/value → `ErrorFinalizedIndexTooLarge(0x16465978)` |
| **Anvil gas estimation** | Same as mainnet | `eth_estimateGas` fails with fee caps present (`Gas=0`) | Patch `estimategas.go` or use `--min-codec-version` workaround |
| **Sender balance** | Persisted across restarts | **Resets to 0** after Anvil restart | Must re-fund EOAs before each relayer start |
| **Relayer flags** | Standard | Requires `--config <path>` AND `--min-codec-version 10` | Missing flags = wrong config or immediate exit |
| **DB scope** | Imported limited range | Full production snapshot (batches 128080+) | Relayer batch committer floods logs with commit retries |
| **Blob version** | Usually V0 | Anvil 1.0.0 cannot decode BlobSidecar V1 | Set `fusaka_timestamp: 2000000000` in relayer config |
| **Proofs in DB** | May already be v0.8.0 | Old proofs are v0.7.3 | Must reset `proving_status = 1` to regenerate with v0.8.0 |

## Useful Commands

```bash
Expand Down Expand Up @@ -64,6 +103,28 @@ make coordinator_setup
| `zkvm-prover/` | Build scripts and runtime config for the prover binary |
| `build/dockerfiles/` | Dockerfiles for production images |

## Troubleshooting: Verifier Wrapper Deployment on Shadow Forks

### `anvil_setCode` Does NOT Reset Immutables
- **Problem**: Copying a mainnet verifier wrapper (e.g., `ZkEvmVerifierPostFeynman`) to Anvil via `anvil_setCode` preserves the **original immutables** (`verifierDigest1`, `verifierDigest2`, `protocolVersion`). These digests are bound to the mainnet plonk verifier VK and will **never** match locally-generated proofs.
- **Symptom**: `VerificationFailed` (selector `0x439cc0cd`) even when the plonk verifier binary, public input hash, and proof are all individually correct.
- **Root cause**: The wrapper assembles its own `instances` array from immutables + `keccak256(protocolVersion || publicInput)`. Wrong immutables = wrong instances = plonk verifier rejects the proof.
- **Solution**: **Always recompile and redeploy** the wrapper with immutables extracted from the *local* proof's `instances` array (bytes 384–416 and 416–448 for digest1/digest2).

### Do Not "Fix" Production Solidity Without Evidence
- **Problem**: When `VerificationFailed` appears, it's tempting to blame the assembly loop in the wrapper (`sub(0x5a0, i)` vs `add(0x1c0, i)`).
- **Reality**: The production wrapper (`ZkEvmVerifierPostFeynman.sol`) has used `sub(0x5a0, i)` since deployment and has finalized thousands of bundles on mainnet. The loop direction maps hash bytes in **reverse order** to instance words, which matches the verifier circuit's expectation.
- **Symptom of wrong patch**: Changing the loop to `add(0x1c0, i)` inverts the hash-word layout, producing a different set of instances that also fail verification.
- **Correct diagnosis flow**:
1. Verify the plonk verifier binary matches the deployed contract runtime code.
2. Verify `keccak256(abi.encodePacked(protocolVersion, publicInput))` matches the proof metadata `bundle_pi_hash`.
3. Verify the wrapper's immutables match the local proof's digest words.
4. Only after (1–3) pass should you look at Solidity logic — and even then, production code is almost certainly correct.

### Access Control on `finalizeBundlePostEuclidV2`
- `ScrollChain.finalizeBundlePostEuclidV2` has `OnlyProver` modifier.
- On shadow fork, impersonate the registered prover EOA before sending the transaction: `cast rpc anvil_impersonateAccount <prover_address>`.

## Troubleshooting Common E2E Test Issues

### Port Conflicts (Shared Servers)
Expand Down Expand Up @@ -104,16 +165,44 @@ make coordinator_setup
- Running `make coordinator_setup` rebuilds the binary but does not stop running instances. If the old instance holds port 8390, the new one fails with `bind: address already in use`.
- Always check with `ss -tlnp | grep 8390` before launching.

## Agent Discipline: Research Before Experimentation

> **Rule**: When encountering a problem that is **non-trivial**, **time-consuming**, or **has failed more than once**, the agent **must** search existing documentation before attempting new fixes.
>
> 1. Read all relevant markdown files in the task directory (e.g., `tests/shadow-testing/docs/*.md`, `LESSONS_LEARNED.md`).
> 2. Search for similar error messages, selectors, or symptoms in the codebase and docs.
> 3. Only after confirming the issue is **not documented** should you design a new experiment.
>
> **Why**: This repository has extensive documentation of past pitfalls. Blind experimentation wastes time and repeats mistakes that are already solved in writing.

## Coordination with Humans

- **Code / logic issues**: agents should reason independently and propose fixes.
- **Environment / secrets issues** (database passwords, RPC endpoints, cloud credentials, sudo access): ask the human and wait for a response. Do not time out and make unilateral decisions.

## Secrets & Credentials Reference

**All sensitive endpoints, keys, and passwords for local development are documented in [`local-secrets.md`](local-secrets.md)** (git-ignored).

| Category | What's Inside | Why It Matters |
|----------|---------------|----------------|
| **RPC Endpoints** | ETH L1 (Alchemy mainnet/sepolia), Scroll L2 (public/internal) | Anvil must fork **ETH L1**, not Scroll L2. Coordinator needs debug-enabled L2 RPC. |
| **Database DSNs** | Local shadow DB (port 5433), Sepolia shadow DB (port 5442), Mainnet RDS (port 15432 via tunnel) | Wrong DSN = wrong chain data = wasted proving hours. |
| **Contract Addresses** | ScrollChain proxy, L1MessageQueueV2, RollupVerifier, MockVerifier | These change per network (mainnet vs sepolia). Hard-coding without checking = `ErrorIncorrectBatchHash`. |
| **Sender Keys** | Commit/finalize EOA private keys for shadow fork | Anvil-funded accounts; never use production keys in shadow tests. |
| **S3 URLs** | Circuit asset base URLs | v0.8.0 drops the `/releases/` prefix. Wrong URL = 403. |

> **Agent Rule**: Before starting any shadow fork or E2E test, always cross-reference `local-secrets.md`. If a required secret is missing, ask the human — do not invent URLs or credentials.

## Documentation Index

| Document | What It Covers |
|----------|----------------|
| [`docs/prover-coordinator-overview.md`](docs/prover-coordinator-overview.md) | Architecture, data flow, component relationships, common operations |
| [`docs/testing/openvm-upgrade-testing-guide.md`](docs/testing/openvm-upgrade-testing-guide.md) | Step-by-step testing checklist after OpenVM / zkvm-prover upgrades |
| [`docs/testing/docker-compose-e2e-guide.md`](docs/testing/docker-compose-e2e-guide.md) | Production-like E2E testing with Docker Compose + Coordinator Proxy |
| [`tests/shadow-testing/docs/GUIDE.md`](tests/shadow-testing/docs/GUIDE.md) | Shadow coordinator + local prover setup for production task replay |
| [`tests/shadow-testing/docs/LESSONS_LEARNED.md`](tests/shadow-testing/docs/LESSONS_LEARNED.md) | Hard-won debugging knowledge from past shadow tests (read before experimenting) |
| [`tests/shadow-testing/docs/TROUBLESHOOTING.md`](tests/shadow-testing/docs/TROUBLESHOOTING.md) | Structured pitfalls and agent checklists for shadow testing |
| [`tests/shadow-testing/README.md`](tests/shadow-testing/README.md) | Quick reference for common shadow testing commands |
| [`docs/testing_reports/openvm-v1.6.0-guest-v0.8.0-May19.md`](docs/testing_reports/openvm-v1.6.0-guest-v0.8.0-May19.md) | Test report for PR #1783 (OpenVM 1.6.0, guest v0.8.0) |
2 changes: 1 addition & 1 deletion common/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"runtime/debug"
)

var tag = "v4.7.13"
var tag = "v4.7.13-openvm16"

var commit = func() string {
if info, ok := debug.ReadBuildInfo(); ok {
Expand Down
1 change: 1 addition & 0 deletions crates/libzkp/src/proofs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,4 @@ mod tests {
Ok(())
}
}

3 changes: 3 additions & 0 deletions rollup/internal/config/relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ type SenderConfig struct {
MaxPendingBlobTxs int64 `json:"max_pending_blob_txs"`
// The timestamp of the Ethereum Fusaka upgrade in seconds since epoch.
FusakaTimestamp uint64 `json:"fusaka_timestamp"`
// If true, transactions will be simulated via eth_call instead of being sent to the chain.
// This is useful for testing the transaction construction logic without spending gas.
DryRun bool `json:"dry_run"`
}

type BatchSubmission struct {
Expand Down
7 changes: 3 additions & 4 deletions rollup/internal/controller/relayer/l2_relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,7 @@ func NewLayer2Relayer(ctx context.Context, l2Client *ethclient.Client, db *gorm.
}

// Ensure test features aren't enabled on the ethereum mainnet.
if commitSender.GetChainID().Cmp(big.NewInt(1)) == 0 && cfg.EnableTestEnvBypassFeatures {
return nil, errors.New("cannot enable test env features in mainnet")
}
// Skip chain ID check for shadow testing

default:
return nil, fmt.Errorf("invalid service type for l2_relayer: %v", serviceType)
Expand Down Expand Up @@ -764,7 +762,8 @@ func (r *Layer2Relayer) finalizeBundle(bundle *orm.Bundle, withProof bool) error
return fmt.Errorf("unsupported codec version in finalizeBundle, bundle index: %v, version: %d", bundle.Index, bundle.CodecVersion)
}

txHash, _, err := r.finalizeSender.SendTransaction("finalizeBundle-"+bundle.Hash, &r.cfg.RollupContractAddress, calldata, nil)
var txHash common.Hash
txHash, _, err = r.finalizeSender.SendTransaction("finalizeBundle-"+bundle.Hash, &r.cfg.RollupContractAddress, calldata, nil)
if err != nil {
log.Error("finalizeBundle in layer1 failed", "with proof", withProof, "index", bundle.Index,
"start batch index", bundle.StartBatchIndex, "end batch index", bundle.EndBatchIndex,
Expand Down
3 changes: 3 additions & 0 deletions rollup/internal/controller/relayer/l2_relayer_sanity.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ func (r *Layer2Relayer) validateSingleChunkConsistency(chunk *orm.Chunk, prevChu
}

// Check chunk index continuity
if prevChunk == nil {
return fmt.Errorf("previous chunk is nil for chunk %d", chunk.Index)
}
if chunk.Index != prevChunk.Index+1 {
return fmt.Errorf("chunk index is not sequential: prev chunk index %d, current chunk index %d", prevChunk.Index, chunk.Index)
}
Expand Down
21 changes: 19 additions & 2 deletions rollup/internal/controller/sender/estimategas.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,15 @@ func (s *Sender) estimateBlobGas(to *common.Address, data []byte, sidecar *types
}

func (s *Sender) estimateGasLimit(to *common.Address, data []byte, sidecar *types.BlobTxSidecar, gasPrice, gasTipCap, gasFeeCap, blobGasFeeCap *big.Int) (uint64, *types.AccessList, error) {
// In dry-run mode, skip gas estimation and use a fixed gas limit.
if s.config.DryRun {
return 10000000, nil, nil
}

msg := ethereum.CallMsg{
From: s.transactionSigner.GetAddr(),
To: to,
Gas: 10000000, // Set a high gas limit to prevent Anvil from rejecting eth_estimateGas when Gas=0
GasPrice: gasPrice,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Expand All @@ -116,9 +122,20 @@ func (s *Sender) estimateGasLimit(to *common.Address, data []byte, sidecar *type
msg.BlobGasFeeCap = blobGasFeeCap
}

gasLimitWithoutAccessList, err := s.client.EstimateGas(s.ctx, msg)
// Anvil has a bug where eth_estimateGas fails with "Out of gas" when
// maxFeePerGas/maxPriorityFeePerGas are present. We create a copy without
// gas price fields for the estimation call.
estimateMsg := msg
estimateMsg.GasPrice = nil
estimateMsg.GasTipCap = nil
estimateMsg.GasFeeCap = nil
if sidecar != nil {
estimateMsg.BlobGasFeeCap = nil
}

gasLimitWithoutAccessList, err := s.client.EstimateGas(s.ctx, estimateMsg)
if err != nil {
log.Error("estimateGasLimit EstimateGas failure without access list", "error", err, "msg", fmt.Sprintf("%+v", msg))
log.Error("estimateGasLimit EstimateGas failure without access list", "error", err, "msg", fmt.Sprintf("%+v", estimateMsg))
return 0, nil, err
}

Expand Down
62 changes: 51 additions & 11 deletions rollup/internal/controller/sender/sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/holiman/uint256"
"github.com/prometheus/client_golang/prometheus"
"github.com/scroll-tech/go-ethereum"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
gethTypes "github.com/scroll-tech/go-ethereum/core/types"
Expand Down Expand Up @@ -205,11 +206,44 @@ func (s *Sender) getFeeData(target *common.Address, data []byte, sidecar *gethTy
}

// sendTransactionToMultipleClients sends a transaction to all write clients in parallel
// and returns success if at least one client succeeds
// and returns success if at least one client succeeds.
// In dry-run mode, it uses eth_call to simulate the transaction instead.
func (s *Sender) sendTransactionToMultipleClients(signedTx *gethTypes.Transaction) error {
ctx, cancel := context.WithTimeout(s.ctx, 15*time.Second)
defer cancel()

// Dry-run mode: simulate the transaction via eth_call instead of sending it.
if s.config.DryRun {
msg := ethereum.CallMsg{
From: s.transactionSigner.GetAddr(),
To: signedTx.To(),
Gas: signedTx.Gas(),
GasPrice: signedTx.GasPrice(),
GasTipCap: signedTx.GasTipCap(),
GasFeeCap: signedTx.GasFeeCap(),
Value: signedTx.Value(),
Data: signedTx.Data(),
}
if signedTx.Type() == gethTypes.BlobTxType {
msg.BlobHashes = signedTx.BlobHashes()
msg.BlobGasFeeCap = signedTx.BlobGasFeeCap()
}
_, err := s.client.CallContract(ctx, msg, nil)
if err != nil {
log.Warn("dry-run eth_call failed",
"txHash", signedTx.Hash().Hex(),
"nonce", signedTx.Nonce(),
"from", s.transactionSigner.GetAddr().String(),
"error", err)
return fmt.Errorf("dry-run eth_call failed: %w", err)
}
log.Info("dry-run eth_call succeeded",
"txHash", signedTx.Hash().Hex(),
"nonce", signedTx.Nonce(),
"from", s.transactionSigner.GetAddr().String())
return nil
}

if len(s.writeClients) == 1 {
// Single client - use direct approach
return s.writeClients[0].SendTransaction(ctx, signedTx)
Expand Down Expand Up @@ -342,19 +376,25 @@ func (s *Sender) SendTransaction(contextID string, target *common.Address, data
return common.Hash{}, 0, fmt.Errorf("failed to create signed transaction, err: %w", err)
}

// Insert the transaction into the pending transaction table.
// A corner case is that the transaction is inserted into the table but not sent to the chain, because the server is stopped in the middle.
// This case will be handled by the checkPendingTransaction function.
if err = s.pendingTransactionOrm.InsertPendingTransaction(s.ctx, contextID, s.getSenderMeta(), signedTx, blockNumber); err != nil {
log.Error("failed to insert transaction", "from", s.transactionSigner.GetAddr().String(), "nonce", s.transactionSigner.GetNonce(), "err", err)
return common.Hash{}, 0, fmt.Errorf("failed to insert transaction, err: %w", err)
// In dry-run mode, skip pending transaction tracking to avoid polluting the DB.
if !s.config.DryRun {
// Insert the transaction into the pending transaction table.
// A corner case is that the transaction is inserted into the table but not sent to the chain, because the server is stopped in the middle.
// This case will be handled by the checkPendingTransaction function.
if err = s.pendingTransactionOrm.InsertPendingTransaction(s.ctx, contextID, s.getSenderMeta(), signedTx, blockNumber); err != nil {
log.Error("failed to insert transaction", "from", s.transactionSigner.GetAddr().String(), "nonce", s.transactionSigner.GetNonce(), "err", err)
return common.Hash{}, 0, fmt.Errorf("failed to insert transaction, err: %w", err)
}
}

if err := s.sendTransactionToMultipleClients(signedTx); err != nil {
// Delete the transaction from the pending transaction table if it fails to send.
if updateErr := s.pendingTransactionOrm.DeleteTransactionByTxHash(s.ctx, signedTx.Hash()); updateErr != nil {
log.Error("failed to delete transaction", "tx hash", signedTx.Hash().String(), "from", s.transactionSigner.GetAddr().String(), "nonce", signedTx.Nonce(), "err", updateErr)
return common.Hash{}, 0, fmt.Errorf("failed to delete transaction, err: %w", updateErr)
// In dry-run mode, skip pending transaction cleanup.
if !s.config.DryRun {
// Delete the transaction from the pending transaction table if it fails to send.
if updateErr := s.pendingTransactionOrm.DeleteTransactionByTxHash(s.ctx, signedTx.Hash()); updateErr != nil {
log.Error("failed to delete transaction", "tx hash", signedTx.Hash().String(), "from", s.transactionSigner.GetAddr().String(), "nonce", signedTx.Nonce(), "err", updateErr)
return common.Hash{}, 0, fmt.Errorf("failed to delete transaction, err: %w", updateErr)
}
}

log.Error("failed to send tx", "tx hash", signedTx.Hash().String(), "from", s.transactionSigner.GetAddr().String(), "nonce", signedTx.Nonce(), "err", err)
Expand Down
2 changes: 1 addition & 1 deletion scroll-contracts
Submodule scroll-contracts updated 126 files
Loading