Skip to content
Merged
Changes from all commits
Commits
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
127 changes: 100 additions & 27 deletions crates/optimism/tests/proofs/prune/prune_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,121 @@ import (

"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-service/apis"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/op-rs/op-geth/proofs/utils"
"github.com/stretchr/testify/require"
)

func TestPruneProofStorage(gt *testing.T) {
// Steps:
// 1) Create some tx and validate proof for a block (pre-prune):
// 2) Wait for that specific block to be pruned:
// - Ensure the chain advances enough so the pruner *can* move `earliest` past `targetBlock`
// (i.e. latest >= targetBlock + proofWindow).
// - Poll debug_proofsSyncStatus until earliest > targetBlock (meaning targetBlock is now pruned).
//
// 3) Call validate checks for getProof and check everything is consistent for the new earliest block.
func TestPruneProofStorageWithGetProofConsistency(gt *testing.T) {
t := devtest.SerialT(gt)
ctx := t.Ctx()

sys := utils.NewMixedOpProofPreset(t)

var proofWindow = uint64(200) // Defined in the devnet yaml
var pruneDetectTimeout = time.Minute * 5 // An expected time within the prune should be detected.
// Defined in the devnet yaml
var proofWindow = uint64(200)

// An expected time within the prune should be detected.
var pruneDetectTimeout = 5 * time.Minute

opRethELNode := sys.RethL2ELNode()
ethClient := opRethELNode.Escape().EthClient()

// -----------------------------
// (1) Create tx + validate proof pre-prune
// -----------------------------
const numAccounts = 2
const initialFunding = 10

accounts := sys.FunderL2.NewFundedEOAs(numAccounts, eth.Ether(initialFunding))
recipient := sys.FunderL2.NewFundedEOA(eth.Ether(1))
recipientAddr := recipient.Address()
transferAmount := eth.Ether(1)

syncStatus := getProofSyncStatus(t, opRethELNode.Escape().EthClient())
t.Log("Initial sync status:", syncStatus)
distance := syncStatus.Latest - syncStatus.Earliest
t.Log("Sending transactions from both accounts (to create state changes)")
tx0 := accounts[0].Transfer(recipientAddr, transferAmount)
tx1 := accounts[1].Transfer(recipientAddr, transferAmount)

if distance < proofWindow {
// Wait till we reach proof window
t.Logf("Waiting for block %d", syncStatus.Earliest+proofWindow)
opRethELNode.WaitForBlockNumber(syncStatus.Earliest + proofWindow)
receipt0, err := tx0.Included.Eval(ctx)
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, receipt0.Status)

receipt1, err := tx1.Included.Eval(ctx)
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, receipt1.Status)

// Choose a deterministic target block: the later of the two inclusion blocks.
targetBlock := receipt0.BlockNumber.Uint64()
if receipt1.BlockNumber.Uint64() > targetBlock {
targetBlock = receipt1.BlockNumber.Uint64()
}
// Now we need to wait for pruner to execute pruning, which can be done anytime within 1 minute (pruner prune interval = 1 min)
startTime := time.Now()
var newSyncStatus proofSyncStatus
t.Logf("Target block for proof validation (pre-prune): %d", targetBlock)

// Make sure validator has the block too (keeps the test stable).
sys.L2ELValidatorNode().WaitForBlockNumber(targetBlock)

// Pre-prune proof verification at targetBlock.
// This verifies the proof against the block's state root (efficient correctness check).
t.Logf("Pre-prune: verifying getProof proofs at block %d", targetBlock)
utils.FetchAndVerifyProofs(t, sys, accounts[0].Address(), []common.Hash{}, targetBlock)
utils.FetchAndVerifyProofs(t, sys, accounts[1].Address(), []common.Hash{}, targetBlock)
t.Log("Pre-prune: proofs verified successfully")

// -----------------------------
// (2) Wait until targetBlock is pruned (earliest > targetBlock)
// -----------------------------
initialStatus := getProofSyncStatus(t, ethClient)
t.Logf("Initial proofs sync status: earliest=%d latest=%d", initialStatus.Earliest, initialStatus.Latest)

// Ensure we advance far enough that pruning *can* move earliest past targetBlock.
// If latest < targetBlock + proofWindow, earliest cannot advance beyond targetBlock yet.
requiredLatest := targetBlock + proofWindow
if initialStatus.Latest < requiredLatest {
t.Logf("Waiting for chain to advance to at least block %d so pruning can pass targetBlock", requiredLatest)
opRethELNode.WaitForBlockNumber(requiredLatest)
}

t.Logf("Waiting for pruner to advance earliest past targetBlock=%d ...", targetBlock)
waitUntil := time.Now().Add(pruneDetectTimeout)

var prunedStatus proofSyncStatus
for {
// Get sync status each Second
if time.Since(startTime) > pruneDetectTimeout {
t.Error("Pruner did not prune proof storage within the interval")
return
if time.Now().After(waitUntil) {
t.Errorf("Timed out waiting for prune: earliest did not advance past targetBlock=%d within %s", targetBlock, pruneDetectTimeout)
}
newSyncStatus = getProofSyncStatus(t, opRethELNode.Escape().EthClient())
if syncStatus.Earliest != newSyncStatus.Earliest {

prunedStatus = getProofSyncStatus(t, ethClient)
t.Logf("Polling proofs sync status: earliest=%d latest=%d (target=%d)", prunedStatus.Earliest, prunedStatus.Latest, targetBlock)

// This is the key condition: the specific block we validated earlier is now out of window.
if prunedStatus.Earliest > targetBlock {
break
}
t.Log("Waiting on earliest state to be changed: ", syncStatus.Earliest)
time.Sleep(time.Second * 5)

time.Sleep(5 * time.Second)
}
// Check how many has been pruned - we should have current proof window intake
currentProofWindow := newSyncStatus.Latest - newSyncStatus.Earliest
t.Log("Sync status:", syncStatus)
require.GreaterOrEqual(t, currentProofWindow, proofWindow, "Pruner has changed the proof window")
t.Logf("Successfully pruned proof storage. sync status: %v", syncStatus)

currentProofWindow := prunedStatus.Latest - prunedStatus.Earliest
require.GreaterOrEqual(t, currentProofWindow, proofWindow, "pruner should maintain at least the configured proof window")
t.Logf("Detected prune past targetBlock. Now earliest=%d latest=%d window=%d", prunedStatus.Earliest, prunedStatus.Latest, currentProofWindow)

// -----------------------------
// (3) Post-prune consistency checks for getProof
// -----------------------------
t.Logf("Post-prune: expecting getProof verification to succeed at new earliest block=%d", prunedStatus.Earliest)
utils.FetchAndVerifyProofs(t, sys, accounts[0].Address(), []common.Hash{}, prunedStatus.Earliest)
utils.FetchAndVerifyProofs(t, sys, accounts[1].Address(), []common.Hash{}, prunedStatus.Earliest)
t.Log("Post-prune: getProof consistency checks passed")
}

type proofSyncStatus struct {
Expand All @@ -59,7 +132,7 @@ func getProofSyncStatus(t devtest.T, client apis.EthClient) proofSyncStatus {
var result proofSyncStatus
err := client.RPC().CallContext(t.Ctx(), &result, "debug_proofsSyncStatus")
if err != nil {
t.Error(err)
t.Errorf("debug_proofsSyncStatus call failed: %v", err)
}
return result
}
Loading