Skip to content

Commit

Permalink
Add support for multiple withdrawals in a single L3 tx (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdehoog authored Jan 10, 2025
1 parent 92d5b60 commit 54537dc
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 34 deletions.
3 changes: 2 additions & 1 deletion op-withdrawer/example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 17 additions & 7 deletions op-withdrawer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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{})
Expand All @@ -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 {
Expand Down
75 changes: 49 additions & 26 deletions op-withdrawer/withdrawals/withdrawals.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package withdrawals
import (
"context"
"errors"
"log"
"fmt"
"math/big"
"time"

Expand All @@ -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 {
Expand All @@ -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) {
Expand Down

0 comments on commit 54537dc

Please sign in to comment.