Commit e85c4837 authored by Adrian Sutton's avatar Adrian Sutton

op-e2e: Simplify and correct wait period when finalizing withdrawals.

parent 69ba4ea1
...@@ -78,3 +78,24 @@ func WaitFor(ctx context.Context, rate time.Duration, cb func() (bool, error)) e ...@@ -78,3 +78,24 @@ func WaitFor(ctx context.Context, rate time.Duration, cb func() (bool, error)) e
} }
} }
} }
func WaitAndGet[T interface{}](ctx context.Context, pollRate time.Duration, get func() (T, error), predicate func(T) bool) (T, error) {
tick := time.NewTicker(pollRate)
defer tick.Stop()
var nilT T
for {
select {
case <-ctx.Done():
return nilT, ctx.Err()
case <-tick.C:
val, err := get()
if err != nil {
return nilT, err
}
if predicate(val) {
return val, nil
}
}
}
}
...@@ -511,7 +511,7 @@ func TestMixedWithdrawalValidity(t *testing.T) { ...@@ -511,7 +511,7 @@ func TestMixedWithdrawalValidity(t *testing.T) {
// Wait for the finalization period, then we can finalize this withdrawal. // Wait for the finalization period, then we can finalize this withdrawal.
ctx, cancel = context.WithTimeout(context.Background(), 40*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) ctx, cancel = context.WithTimeout(context.Background(), 40*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
blockNumber, err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, receipt.BlockNumber) blockNumber, err := withdrawals.WaitForOutputRootPublished(ctx, l1Client, predeploys.DevOptimismPortalAddr, receipt.BlockNumber)
cancel() cancel()
require.Nil(t, err) require.Nil(t, err)
...@@ -642,7 +642,7 @@ func TestMixedWithdrawalValidity(t *testing.T) { ...@@ -642,7 +642,7 @@ func TestMixedWithdrawalValidity(t *testing.T) {
// Wait for finalization and then create the Finalized Withdrawal Transaction // Wait for finalization and then create the Finalized Withdrawal Transaction
ctx, cancel = context.WithTimeout(context.Background(), 45*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) ctx, cancel = context.WithTimeout(context.Background(), 45*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel() defer cancel()
_, err = withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, header.Number) err = withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, header.Number)
require.Nil(t, err) require.Nil(t, err)
// Finalize withdrawal // Finalize withdrawal
......
...@@ -79,7 +79,7 @@ func defaultWithdrawalTxOpts() *WithdrawalTxOpts { ...@@ -79,7 +79,7 @@ func defaultWithdrawalTxOpts() *WithdrawalTxOpts {
func ProveAndFinalizeWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, l2Node *node.Node, ethPrivKey *ecdsa.PrivateKey, l2WithdrawalReceipt *types.Receipt) (*types.Receipt, *types.Receipt) { func ProveAndFinalizeWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, l2Node *node.Node, ethPrivKey *ecdsa.PrivateKey, l2WithdrawalReceipt *types.Receipt) (*types.Receipt, *types.Receipt) {
params, proveReceipt := ProveWithdrawal(t, cfg, l1Client, l2Node, ethPrivKey, l2WithdrawalReceipt) params, proveReceipt := ProveWithdrawal(t, cfg, l1Client, l2Node, ethPrivKey, l2WithdrawalReceipt)
finalizeReceipt := FinalizeWithdrawal(t, cfg, l1Client, ethPrivKey, l2WithdrawalReceipt, params) finalizeReceipt := FinalizeWithdrawal(t, cfg, l1Client, ethPrivKey, proveReceipt, params)
return proveReceipt, finalizeReceipt return proveReceipt, finalizeReceipt
} }
...@@ -87,7 +87,8 @@ func ProveWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client, ...@@ -87,7 +87,8 @@ func ProveWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Client,
// Get l2BlockNumber for proof generation // Get l2BlockNumber for proof generation
ctx, cancel := context.WithTimeout(context.Background(), 40*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 40*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel() defer cancel()
blockNumber, err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, l2WithdrawalReceipt.BlockNumber)
blockNumber, err := withdrawals.WaitForOutputRootPublished(ctx, l1Client, predeploys.DevOptimismPortalAddr, l2WithdrawalReceipt.BlockNumber)
require.Nil(t, err) require.Nil(t, err)
rpcClient, err := rpc.Dial(l2Node.WSEndpoint()) rpcClient, err := rpc.Dial(l2Node.WSEndpoint())
...@@ -142,7 +143,7 @@ func FinalizeWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Clie ...@@ -142,7 +143,7 @@ func FinalizeWithdrawal(t *testing.T, cfg SystemConfig, l1Client *ethclient.Clie
// Wait for finalization and then create the Finalized Withdrawal Transaction // Wait for finalization and then create the Finalized Withdrawal Transaction
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel() defer cancel()
_, err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, withdrawalReceipt.BlockNumber) err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, withdrawalReceipt.BlockNumber)
require.Nil(t, err) require.Nil(t, err)
opts, err := bind.NewKeyedTransactorWithChainID(privKey, cfg.L1ChainIDBig()) opts, err := bind.NewKeyedTransactorWithChainID(privKey, cfg.L1ChainIDBig())
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -22,103 +23,74 @@ import ( ...@@ -22,103 +23,74 @@ import (
var MessagePassedTopic = crypto.Keccak256Hash([]byte("MessagePassed(uint256,address,address,uint256,uint256,bytes,bytes32)")) var MessagePassedTopic = crypto.Keccak256Hash([]byte("MessagePassed(uint256,address,address,uint256,uint256,bytes,bytes32)"))
// WaitForFinalizationPeriod waits until there is OutputProof for an L2 block number larger than the supplied l2BlockNumber // WaitForOutputRootPublished waits until there is an output published for an L2 block number larger than the supplied l2BlockNumber
// and that the output is finalized. // This function polls and can block for a very long time if used on mainnet.
// This functions polls and can block for a very long time if used on mainnet. // This returns the block number to use for proof generation.
// This returns the block number to use for the proof generation. func WaitForOutputRootPublished(ctx context.Context, client *ethclient.Client, portalAddr common.Address, l2BlockNumber *big.Int) (uint64, error) {
func WaitForFinalizationPeriod(ctx context.Context, client *ethclient.Client, portalAddr common.Address, l2BlockNumber *big.Int) (uint64, error) {
l2BlockNumber = new(big.Int).Set(l2BlockNumber) // Don't clobber caller owned l2BlockNumber l2BlockNumber = new(big.Int).Set(l2BlockNumber) // Don't clobber caller owned l2BlockNumber
opts := &bind.CallOpts{Context: ctx} opts := &bind.CallOpts{Context: ctx}
portal, err := bindings.NewOptimismPortalCaller(portalAddr, client) l2OO, err := createL2OOCaller(ctx, client, portalAddr)
if err != nil {
return 0, err
}
l2OOAddress, err := portal.L2ORACLE(opts)
if err != nil { if err != nil {
return 0, err return 0, err
} }
l2OO, err := bindings.NewL2OutputOracleCaller(l2OOAddress, client)
getL2BlockFromLatestOutput := func() (*big.Int, error) { return l2OO.LatestBlockNumber(opts) }
outputBlockNum, err := e2eutils.WaitAndGet[*big.Int](ctx, time.Second, getL2BlockFromLatestOutput, func(latest *big.Int) bool {
return latest.Cmp(l2BlockNumber) >= 0
})
if err != nil { if err != nil {
return 0, err return 0, err
} }
submissionInterval, err := l2OO.SUBMISSIONINTERVAL(opts) return outputBlockNum.Uint64(), nil
}
// WaitForFinalizationPeriod waits until the L1 chain has progressed far enough that l1ProvingBlockNum has completed
// the finalization period.
// This functions polls and can block for a very long time if used on mainnet.
func WaitForFinalizationPeriod(ctx context.Context, client *ethclient.Client, portalAddr common.Address, l1ProvingBlockNum *big.Int) error {
l1ProvingBlockNum = new(big.Int).Set(l1ProvingBlockNum) // Don't clobber caller owned l1ProvingBlockNum
opts := &bind.CallOpts{Context: ctx}
// Load finalization period
l2OO, err := createL2OOCaller(ctx, client, portalAddr)
if err != nil { if err != nil {
return 0, err return fmt.Errorf("create L2OOCaller: %w", err)
}
// Convert blockNumber to submission interval boundary
rem := new(big.Int)
l2BlockNumber, rem = l2BlockNumber.DivMod(l2BlockNumber, submissionInterval, rem)
if rem.Cmp(common.Big0) != 0 {
l2BlockNumber = l2BlockNumber.Add(l2BlockNumber, common.Big1)
} }
l2BlockNumber = l2BlockNumber.Mul(l2BlockNumber, submissionInterval)
finalizationPeriod, err := l2OO.FINALIZATIONPERIODSECONDS(opts) finalizationPeriod, err := l2OO.FINALIZATIONPERIODSECONDS(opts)
if err != nil { if err != nil {
return 0, err return fmt.Errorf("get finalization period: %w", err)
} }
latest, err := l2OO.LatestBlockNumber(opts) provingHeader, err := client.HeaderByNumber(ctx, l1ProvingBlockNum)
if err != nil { if err != nil {
return 0, err return fmt.Errorf("retrieve proving header: %w", err)
} }
// Now poll for the output to be submitted on chain targetTimestamp := new(big.Int).Add(new(big.Int).SetUint64(provingHeader.Time), finalizationPeriod)
var ticker *time.Ticker
diff := new(big.Int).Sub(l2BlockNumber, latest)
if diff.Cmp(big.NewInt(10)) > 0 {
ticker = time.NewTicker(time.Minute)
} else {
ticker = time.NewTicker(time.Second)
}
loop:
for {
select {
case <-ticker.C:
latest, err = l2OO.LatestBlockNumber(opts)
if err != nil {
return 0, err
}
// Already passed the submitted block (likely just equals rather than >= here).
if latest.Cmp(l2BlockNumber) >= 0 {
break loop
}
case <-ctx.Done():
return 0, ctx.Err()
}
}
// Now wait for it to be finalized
output, err := l2OO.GetL2OutputAfter(opts, l2BlockNumber)
if err != nil {
return 0, err
}
if output.OutputRoot == [32]byte{} {
return 0, errors.New("empty output root. likely no proposal at timestamp")
}
targetTimestamp := new(big.Int).Add(output.Timestamp, finalizationPeriod)
targetTime := time.Unix(targetTimestamp.Int64(), 0) targetTime := time.Unix(targetTimestamp.Int64(), 0)
// Assume clock is relatively correct // Assume clock is relatively correct
time.Sleep(time.Until(targetTime)) time.Sleep(time.Until(targetTime))
// Poll for L1 Block to have a time greater than the target time // Poll for L1 Block to have a time greater than the target time
ticker = time.NewTicker(time.Second) return e2eutils.WaitFor(ctx, time.Second, func() (bool, error) {
for { header, err := client.HeaderByNumber(ctx, nil)
select { if err != nil {
case <-ticker.C: return false, fmt.Errorf("retrieve latest header: %w", err)
header, err := client.HeaderByNumber(ctx, nil)
if err != nil {
return 0, err
}
if header.Time > targetTimestamp.Uint64() {
return l2BlockNumber.Uint64(), nil
}
case <-ctx.Done():
return 0, ctx.Err()
} }
} return header.Time > targetTimestamp.Uint64(), nil
})
}
func createL2OOCaller(ctx context.Context, client *ethclient.Client, portalAddr common.Address) (*bindings.L2OutputOracleCaller, error) {
portal, err := bindings.NewOptimismPortalCaller(portalAddr, client)
if err != nil {
return nil, fmt.Errorf("create OptimismPortalCaller: %w", err)
}
l2OOAddress, err := portal.L2ORACLE(&bind.CallOpts{Context: ctx})
if err != nil {
return nil, fmt.Errorf("create L2ORACLE: %w", err)
}
return bindings.NewL2OutputOracleCaller(l2OOAddress, client)
} }
type ProofClient interface { type ProofClient interface {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment