-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
op-e2e: Add FPP test for empty blocks #5573
Conversation
|
✅ Deploy Preview for opstack-docs canceled.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to play with the code directly to think through all the details here and wound up with the code below. The main change is that I've dropped the verifier node and monitored the safe head to know when empty blocks were inserted because the sequence window elapsed.
// TestVerifyL2OutputRoot_EmptyBlock asserts that the program can verify the output root of an empty block
// induced by missing batches.
// Setup is as follows:
// - create initial conditions and agreed l2 state
// - stop the batch submitter to induce empty blocks
// - wait for the seq window to expire so we can observe empty blocks
// - select an empty block as our claim
// - reboot the batch submitter
// - update the state root via a tx
// - run program
func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool) {
InitParallel(t)
ctx := context.Background()
cfg := DefaultSystemConfig(t)
// We don't need a verifier - just the sequencer is enough
delete(cfg.Nodes, "verifier")
// Use a small sequencer window size to avoid test timeout while waiting for empty blocks
// But not too small to ensure that the updated state after claim selection is published
cfg.DeployConfig.SequencerWindowSize = 16
sys, err := cfg.Start()
require.Nil(t, err, "Error starting up system")
defer sys.Close()
log := testlog.Logger(t, log.LvlInfo)
log.Info("genesis", "l2", sys.RollupConfig.Genesis.L2, "l1", sys.RollupConfig.Genesis.L1, "l2_time", sys.RollupConfig.Genesis.L2Time)
l1Client := sys.Clients["l1"]
l2Seq := sys.Clients["sequencer"]
rollupRPCClient, err := rpc.DialContext(context.Background(), sys.RollupNodes["sequencer"].HTTPEndpoint())
require.Nil(t, err)
rollupClient := sources.NewRollupClient(client.NewBaseRPCClient(rollupRPCClient))
verifierRPCClient, err := rpc.DialContext(context.Background(), sys.RollupNodes["sequencer"].HTTPEndpoint())
require.Nil(t, err)
verifierClient := sources.NewRollupClient(client.NewBaseRPCClient(verifierRPCClient))
t.Log("Sending transactions to setup existing state, prior to challenged period")
aliceKey := cfg.Secrets.Alice
receipt := SendL2Tx(t, cfg, l2Seq, aliceKey, func(opts *TxOpts) {
opts.ToAddr = &cfg.Secrets.Addresses().Bob
opts.Value = big.NewInt(1_000)
})
require.NoError(t, waitForSafeHead(ctx, receipt.BlockNumber.Uint64(), rollupClient))
t.Log("Capture current L2 head as agreed starting point")
l2Head := receipt.BlockHash
t.Log("=====Stopping batch submitter=====")
err = sys.BatchSubmitter.Stop(ctx)
require.NoError(t, err, "could not stop batch submitter")
// Wait for the sequencer to catch up with the current L1 head so we know all submitted batches are processed
t.Log("Wait for sequencer to catch up with last submitted batch")
l1HeadNum, err := l1Client.BlockNumber(ctx)
require.NoError(t, err)
_, err = waitForL1OriginOnL2(l1HeadNum, l2Seq, 30*time.Second)
require.NoError(t, err)
// Get the current safe head now that the batcher is stopped
safeBlock, err := l2Seq.BlockByNumber(ctx, big.NewInt(int64(rpc.SafeBlockNumber)))
require.NoError(t, err)
// Wait for safe head to start advancing again when the sequencing window elapses, for at least three blocks
t.Log("Wait for safe head to advance after sequencing window elapses")
require.NoError(t, waitForSafeHead(ctx, safeBlock.NumberU64()+3, rollupClient))
// Use the 2nd empty block as our L2 claim block
t.Log("Determine L2 claim")
l2ClaimBlock, err := l2Seq.BlockByNumber(ctx, big.NewInt(int64(safeBlock.NumberU64()+2)))
require.NoError(t, err, "get L2 claim block number")
l2ClaimBlockNumber := l2ClaimBlock.Number().Uint64()
l2Output, err := verifierClient.OutputAtBlock(ctx, l2ClaimBlockNumber)
require.NoError(t, err, "could not get expected output")
l2Claim := l2Output.OutputRoot
t.Log("=====Restarting batch submitter=====")
err = sys.BatchSubmitter.Start()
require.NoError(t, err, "could not start batch submitter")
t.Log("Add a transaction to the next batch after sequence of empty blocks")
receipt = SendL2Tx(t, cfg, l2Seq, aliceKey, func(opts *TxOpts) {
opts.ToAddr = &cfg.Secrets.Addresses().Bob
opts.Value = big.NewInt(1_000)
opts.Nonce = 1
})
require.NoError(t, waitForSafeHead(ctx, receipt.BlockNumber.Uint64(), rollupClient))
t.Log("Determine L1 head that includes batch after sequence of empty blocks")
l1HeadBlock, err := l1Client.BlockByNumber(ctx, nil)
require.NoError(t, err, "get l1 head block")
l1Head := l1HeadBlock.Hash()
testFaultProofProgramScenario(t, ctx, sys, &FaultProofProgramTestScenario{
L1Head: l1Head,
L2Head: l2Head,
L2Claim: common.Hash(l2Claim),
L2ClaimBlockNumber: l2ClaimBlockNumber,
})
}
I had to increase the timeout in the waitForSafeHead
as well because it takes more than 30 seconds for the sequence window to elapse. Given the timeout shouldn't be reached just setting a fixed wait period that's well after what we expect to wait is fine IMO.
btw the code I posted is just a "this may be helpful" not a "this is definitely the right way to do it" thing. |
For some reason, waiting for a given safe head (via |
This actually works better. Thanks for code suggestion. As for the 30s timeout, it's unavoidable due to seq window waits. Not terribly ideal for local execution. But CI uses a larger timeout so it's fine. |
b520441
to
85ccb78
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just need to pass through detached for the existing test as well I think.
Asserting that the FPP will generate empty blocks when there are missing batches
This PR has been added to the merge queue, and will be merged soon. |
This PR is next in line to be merged, and will be merged as soon as checks pass. |
1 similar comment
This PR is next in line to be merged, and will be merged as soon as checks pass. |
} | ||
|
||
func TestVerifyL2OutputRootEmptyBlockDetached(t *testing.T) { | ||
testVerifyL2OutputRootEmptyBlock(t, true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Description
Add e2e test asserting that the fault proof program handles missing batches and empty blocks.
Metadata