diff --git a/op-withdrawer/example/main.go b/op-withdrawer/example/main.go index 80234fd..0ef0c46 100644 --- a/op-withdrawer/example/main.go +++ b/op-withdrawer/example/main.go @@ -233,10 +233,11 @@ func ProveWithdrawal(ctx context.Context, l1, l2 *ethclient.Client, l2g *gethcli l2OutputBlock, err := withdrawals.WaitForOutputBlock(ctx, outputOracle, withdrawalTxBlock, pollInterval) fmt.Println("done") - tx, err := withdrawals.ProveAndFinalizeWithdrawal(ctx, l2g, l2, opts, outputOracle, portal, withdrawalTxHash, l2OutputBlock) + txs, err := withdrawals.ProveAndFinalizeWithdrawals(ctx, l2g, l2, opts, outputOracle, portal, withdrawalTxHash, l2OutputBlock) if err != nil { log.Fatalf("Error proving and finalizing withdrawal: %v", err) } + tx := txs[0] receipt, err := withdrawals.WaitForReceipt(ctx, l1, tx.Hash(), pollInterval) if err != nil { log.Fatalf("Error waiting for confirmation: %v", err) diff --git a/op-withdrawer/main.go b/op-withdrawer/main.go index a9590fc..0b79901 100644 --- a/op-withdrawer/main.go +++ b/op-withdrawer/main.go @@ -61,7 +61,7 @@ func main() { app.Commands = []*cli.Command{ { Name: "depositHash", - Usage: "Calculate L2 deposit hash from L1 deposit tx", + Usage: "Calculate L2 deposit hash(es) from L1 deposit tx", Action: DepositHash, Flags: []cli.Flag{ L1URLFlag, @@ -70,7 +70,7 @@ func main() { }, { Name: "proveWithdrawal", - Usage: "Prove and finalize an L2 -> L1 withdrawal", + Usage: "Prove and finalize L2 -> L1 withdrawal(s)", Action: Main, Flags: []cli.Flag{ L1URLFlag, @@ -132,17 +132,19 @@ func Main(cliCtx *cli.Context) error { return err } - receipt, err = ProveWithdrawal(ctx, l1, l2, l2g, opts, portal, withdrawalTxHash, receipt.BlockNumber) + receipts, err := ProveWithdrawal(ctx, l1, l2, l2g, opts, portal, withdrawalTxHash, receipt.BlockNumber) if err != nil { return err } - fmt.Printf("Withdrawal proved and finalized: %s\n", receipt.TxHash) + for _, receipt := range receipts { + fmt.Printf("Withdrawal proved and finalized: %s\n", receipt.TxHash) + } return nil } -func ProveWithdrawal(ctx context.Context, l1, l2 *ethclient.Client, l2g *gethclient.Client, opts *bind.TransactOpts, portal *bindings.Portal, withdrawalTxHash common.Hash, withdrawalTxBlock *big.Int) (*types.Receipt, error) { +func ProveWithdrawal(ctx context.Context, l1, l2 *ethclient.Client, l2g *gethclient.Client, opts *bind.TransactOpts, portal *bindings.Portal, withdrawalTxHash common.Hash, withdrawalTxBlock *big.Int) ([]*types.Receipt, error) { pollInterval := 1 * time.Second outputOracleAddress, err := portal.L2Oracle(&bind.CallOpts{}) @@ -158,11 +160,19 @@ func ProveWithdrawal(ctx context.Context, l1, l2 *ethclient.Client, l2g *gethcli l2OutputBlock, err := withdrawals.WaitForOutputBlock(ctx, outputOracle, withdrawalTxBlock, pollInterval) fmt.Println("done") - tx, err := withdrawals.ProveAndFinalizeWithdrawal(ctx, l2g, l2, opts, outputOracle, portal, withdrawalTxHash, l2OutputBlock) + txs, err := withdrawals.ProveAndFinalizeWithdrawals(ctx, l2g, l2, opts, outputOracle, portal, withdrawalTxHash, l2OutputBlock) if err != nil { return nil, err } - return withdrawals.WaitForReceipt(ctx, l1, tx.Hash(), pollInterval) + + receipts := make([]*types.Receipt, len(txs)) + for i, tx := range txs { + receipts[i], err = withdrawals.WaitForReceipt(ctx, l1, tx.Hash(), pollInterval) + if err != nil { + return nil, err + } + } + return receipts, nil } func DepositHash(cliCtx *cli.Context) error { diff --git a/op-withdrawer/withdrawals/withdrawals.go b/op-withdrawer/withdrawals/withdrawals.go index 11acd6f..817f06f 100644 --- a/op-withdrawer/withdrawals/withdrawals.go +++ b/op-withdrawer/withdrawals/withdrawals.go @@ -3,7 +3,7 @@ package withdrawals import ( "context" "errors" - "log" + "fmt" "math/big" "time" @@ -22,12 +22,13 @@ type ProofClient interface { type EthClient interface { TransactionReceipt(context.Context, common.Hash) (*types.Receipt, error) - BlockByNumber(context.Context, *big.Int) (*types.Block, error) + HeaderByNumber(context.Context, *big.Int) (*types.Header, error) } type OutputOracle interface { LatestBlockNumber(opts *bind.CallOpts) (*big.Int, error) GetL2OutputIndexAfter(opts *bind.CallOpts, _l2BlockNumber *big.Int) (*big.Int, error) + LatestOutputIndex(opts *bind.CallOpts) (*big.Int, error) } type Portal interface { @@ -51,38 +52,60 @@ func WaitForOutputBlock(ctx context.Context, outputOracle *bindings.OutputOracle } } -func ProveAndFinalizeWithdrawal(ctx context.Context, l2ProofCl ProofClient, l2Client EthClient, opts *bind.TransactOpts, outputOracle OutputOracle, portal Portal, withdrawalTxHash common.Hash, l2OutputBlock *big.Int) (*types.Transaction, error) { - l2OutputIndex, err := outputOracle.GetL2OutputIndexAfter(&bind.CallOpts{}, l2OutputBlock) +func ProveAndFinalizeWithdrawals(ctx context.Context, l2ProofCl ProofClient, l2Client EthClient, opts *bind.TransactOpts, outputOracle OutputOracle, portal Portal, withdrawalTxHash common.Hash, l2OutputBlock *big.Int) ([]*types.Transaction, error) { + l2OutputIndex, err := outputOracle.LatestOutputIndex(&bind.CallOpts{}) if err != nil { - log.Fatalf("Error getting L2 output index: %v", err) + return nil, fmt.Errorf("error getting output index: %w", err) } - withdrawal, err := withdrawals.ProveWithdrawalParametersForBlock(ctx, l2ProofCl, l2Client, l2Client, withdrawalTxHash, l2OutputBlock, l2OutputIndex) + receipt, err := l2Client.TransactionReceipt(ctx, withdrawalTxHash) if err != nil { - log.Fatalf("Error proving withdrawal parameters: %v", err) + return nil, fmt.Errorf("error getting withdrawal transaction receipt: %w", err) } + evs, err := withdrawals.ParseMessagesPassed(receipt) + if err != nil { + return nil, fmt.Errorf("error parsing withdrawal logs: %w", err) + } + + header, err := l2Client.HeaderByNumber(ctx, l2OutputBlock) + if err != nil { + return nil, fmt.Errorf("error requesting block header: %w", err) + } + + txs := make([]*types.Transaction, len(evs)) + for i, ev := range evs { + withdrawal, err := withdrawals.ProveWithdrawalParametersForEvent(ctx, l2ProofCl, ev, header, l2OutputIndex) + if err != nil { + return nil, fmt.Errorf("error generating withdrawal proof: %w", err) + } + + outputRootProof := bindings.TypesOutputRootProof{ + Version: withdrawal.OutputRootProof.Version, + StateRoot: withdrawal.OutputRootProof.StateRoot, + MessagePasserStorageRoot: withdrawal.OutputRootProof.MessagePasserStorageRoot, + LatestBlockhash: withdrawal.OutputRootProof.LatestBlockhash, + } - outputRootProof := bindings.TypesOutputRootProof{ - Version: withdrawal.OutputRootProof.Version, - StateRoot: withdrawal.OutputRootProof.StateRoot, - MessagePasserStorageRoot: withdrawal.OutputRootProof.MessagePasserStorageRoot, - LatestBlockhash: withdrawal.OutputRootProof.LatestBlockhash, + txs[i], err = portal.ProveAndFinalizeWithdrawalTransaction( + opts, + bindings.TypesWithdrawalTransaction{ + Nonce: withdrawal.Nonce, + Sender: withdrawal.Sender, + Target: withdrawal.Target, + Value: withdrawal.Value, + GasLimit: withdrawal.GasLimit, + Data: withdrawal.Data, + }, + withdrawal.L2OutputIndex, + outputRootProof, + withdrawal.WithdrawalProof, + ) + if err != nil { + return nil, fmt.Errorf("error submitting withdrawal tx: %w", err) + } } - return portal.ProveAndFinalizeWithdrawalTransaction( - opts, - bindings.TypesWithdrawalTransaction{ - Nonce: withdrawal.Nonce, - Sender: withdrawal.Sender, - Target: withdrawal.Target, - Value: withdrawal.Value, - GasLimit: withdrawal.GasLimit, - Data: withdrawal.Data, - }, - withdrawal.L2OutputIndex, - outputRootProof, - withdrawal.WithdrawalProof, - ) + return txs, nil } func WaitForReceipt(ctx context.Context, client EthClient, txHash common.Hash, pollInterval time.Duration) (*types.Receipt, error) {