Skip to content
Closed
Show file tree
Hide file tree
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
20 changes: 12 additions & 8 deletions indexer/integration_tests/bedrock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ import (
"testing"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"

"github.com/ethereum-optimism/optimism/indexer"
"github.com/ethereum-optimism/optimism/indexer/db"
"github.com/ethereum-optimism/optimism/indexer/services/l1"
Expand All @@ -20,12 +29,6 @@ import (
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"

_ "github.com/lib/pq"
)
Expand Down Expand Up @@ -196,8 +199,9 @@ func TestBedrockIndexer(t *testing.T) {

rpcClient, err := rpc.Dial(sys.Nodes["sequencer"].HTTPEndpoint())
require.NoError(t, err)
proofClient := withdrawals.NewClient(rpcClient)
wParams, err := withdrawals.FinalizeWithdrawalParameters(context.Background(), proofClient, wdTx.Hash(), finHeader)
proofCl := gethclient.New(rpcClient)
receiptCl := ethclient.NewClient(rpcClient)
wParams, err := withdrawals.FinalizeWithdrawalParameters(context.Background(), proofCl, receiptCl, wdTx.Hash(), finHeader)
require.NoError(t, err)

l1Opts.Value = big.NewInt(0)
Expand Down
6 changes: 6 additions & 0 deletions op-e2e/actions/l2_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package actions
import (
"errors"

"github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/stretchr/testify/require"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -117,6 +118,11 @@ func (s *L2Engine) EthClient() *ethclient.Client {
return ethclient.NewClient(cl)
}

func (s *L2Engine) GethClient() *gethclient.Client {
cl, _ := s.node.Attach() // never errors
return gethclient.New(cl)
}

func (e *L2Engine) RPCClient() client.RPC {
cl, _ := e.node.Attach() // never errors
return testutils.RPCErrFaker{
Expand Down
96 changes: 93 additions & 3 deletions op-e2e/actions/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,16 @@ func NewL1Bindings(t Testing, l1Cl *ethclient.Client, deployments *e2eutils.Depl
type L2Bindings struct {
L2ToL1MessagePasser *bindings.L2ToL1MessagePasser

WithdrawalsClient *withdrawals.Client
ProofClient withdrawals.ProofClient
}

func NewL2Bindings(t Testing, l2Cl *ethclient.Client, withdrawalsCl *withdrawals.Client) *L2Bindings {
func NewL2Bindings(t Testing, l2Cl *ethclient.Client, proofCl withdrawals.ProofClient) *L2Bindings {
l2ToL1MessagePasser, err := bindings.NewL2ToL1MessagePasser(predeploys.L2ToL1MessagePasserAddr, l2Cl)
require.NoError(t, err)

return &L2Bindings{
L2ToL1MessagePasser: l2ToL1MessagePasser,
WithdrawalsClient: withdrawalsCl,
ProofClient: proofCl,
}
}

Expand Down Expand Up @@ -278,6 +278,8 @@ type CrossLayerUser struct {

// track the last deposit, to easily chain together deposit actions
lastL1DepositTxHash common.Hash

lastL2WithdrawalTxHash common.Hash
}

func NewCrossLayerUser(log log.Logger, priv *ecdsa.PrivateKey, rng *rand.Rand) *CrossLayerUser {
Expand Down Expand Up @@ -354,6 +356,94 @@ func (s *CrossLayerUser) CheckDepositTx(t Testing, l1TxHash common.Hash, index i
}
}

func (s *CrossLayerUser) ActStartWithdrawal(t Testing) {
targetAddr := common.Address{}
if s.L1.txToAddr != nil {
targetAddr = *s.L2.txToAddr
}
tx, err := s.L2.env.Bindings.L2ToL1MessagePasser.InitiateWithdrawal(&s.L2.txOpts, targetAddr, new(big.Int).SetUint64(s.L1.txOpts.GasLimit), s.L1.txCallData)
require.NoError(t, err, "create initiate withdraw tx")
err = s.L2.env.EthCl.SendTransaction(t.Ctx(), tx)
require.NoError(t, err, "must send tx")
s.lastL2WithdrawalTxHash = tx.Hash()
}

// ActCheckStartWithdrawal checks that a previous witdrawal tx was either successful or failed.
func (s *CrossLayerUser) ActCheckStartWithdrawal(success bool) Action {
return func(t Testing) {
s.L2.CheckReceipt(t, success, s.lastL2WithdrawalTxHash)
}
}

func (s *CrossLayerUser) Address() common.Address {
return s.L1.address
}

// ActCompleteWithdrawal creates a L1 withdrawal completion tx for latest withdrawal.
// The tx hash is remembered as the last L1 tx, to check as L1 actor.
// The withdrawal functions like CompleteWithdrawal
func (s *CrossLayerUser) ActCompleteWithdrawal(t Testing) {
s.L1.lastTxHash = s.CompleteWithdrawal(t, s.lastL2WithdrawalTxHash)
}

// CompleteWithdrawal creates a L1 withdrawal completion tx for the given L2 withdrawal tx, returning the tx hash.
// It's an invalid action to attempt to complete a withdrawal that has not passed the L1 finalization period yet
func (s *CrossLayerUser) CompleteWithdrawal(t Testing, l2TxHash common.Hash) common.Hash {
finalizationPeriod, err := s.L1.env.Bindings.OptimismPortal.FINALIZATIONPERIODSECONDS(&bind.CallOpts{})
require.NoError(t, err)

// Figure out when our withdrawal was included
receipt := s.L2.CheckReceipt(t, true, l2TxHash)
l2WithdrawalBlock, err := s.L2.env.EthCl.BlockByNumber(t.Ctx(), receipt.BlockNumber)
require.NoError(t, err)

// Figure out what the Output oracle on L1 has seen so far
l2OutputBlockNr, err := s.L1.env.Bindings.L2OutputOracle.LatestBlockNumber(&bind.CallOpts{})
require.NoError(t, err)
l2OutputBlock, err := s.L2.env.EthCl.BlockByNumber(t.Ctx(), l2OutputBlockNr)
require.NoError(t, err)

// Check if the L2 output is even old enough to include the withdrawal
if l2OutputBlock.NumberU64() < l2WithdrawalBlock.NumberU64() {
t.InvalidAction("the latest L2 output is %d and is not past L2 block %d that includes the withdrawal yet, no withdrawal can be completed yet", l2OutputBlock.NumberU64(), l2WithdrawalBlock.NumberU64())
return common.Hash{}
}

l1Head, err := s.L1.env.EthCl.HeaderByNumber(t.Ctx(), nil)
require.NoError(t, err)

// Check if the withdrawal may be completed yet
if l2OutputBlock.Time()+finalizationPeriod.Uint64() >= l1Head.Time {
t.InvalidAction("withdrawal tx %s was included in L2 block %d (time %d) but L1 only knows of L2 proposal %d (time %d) at head %d (time %d) which has not reached output confirmation yet (period is %d)",
l2TxHash, l2WithdrawalBlock.NumberU64(), l2WithdrawalBlock.Time(), l2OutputBlock.NumberU64(), l2OutputBlock.Time(), finalizationPeriod.Uint64(), l1Head.Number.Uint64(), l1Head.Time)
return common.Hash{}
}

// We generate a proof for the latest L2 output, which shouldn't require archive-node data if it's recent enough.
header, err := s.L2.env.EthCl.HeaderByNumber(t.Ctx(), l2OutputBlockNr)
require.NoError(t, err)
params, err := withdrawals.FinalizeWithdrawalParameters(t.Ctx(), s.L2.env.Bindings.ProofClient, s.L2.env.EthCl, s.lastL2WithdrawalTxHash, header)
require.NoError(t, err)

// Create the withdrawal tx
tx, err := s.L1.env.Bindings.OptimismPortal.FinalizeWithdrawalTransaction(
&s.L1.txOpts,
bindings.TypesWithdrawalTransaction{
Nonce: params.Nonce,
Sender: params.Sender,
Target: params.Target,
Value: params.Value,
GasLimit: params.GasLimit,
Data: params.Data,
},
params.BlockNumber,
params.OutputRootProof,
params.WithdrawalProof,
)
require.NoError(t, err)

// Send the actual tx (since tx opts don't send by default)
err = s.L1.env.EthCl.SendTransaction(t.Ctx(), tx)
require.NoError(t, err, "must send tx")
return tx.Hash()
}
68 changes: 65 additions & 3 deletions op-e2e/actions/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,42 @@ import (

"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"

"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-node/withdrawals"
)

// TestCrossLayerUser tests that common actions of the CrossLayerUser actor work:
// - transact on L1
// - transact on L2
// - deposit on L1
// - withdraw from L2
// - finalize withdrawal on L1
func TestCrossLayerUser(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug)

miner, seqEngine, seq := setupSequencerTest(t, sd, log)
batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{
MinL1TxSize: 0,
MaxL1TxSize: 128_000,
BatcherKey: dp.Secrets.Batcher,
}, seq.RollupClient(), miner.EthClient(), seqEngine.EthClient())
proposer := NewL2Proposer(t, log, &ProposerCfg{
OutputOracleAddr: sd.DeploymentsL1.L2OutputOracleProxy,
ProposerKey: dp.Secrets.Proposer,
AllowNonFinalized: true,
}, miner.EthClient(), seq.RollupClient())

// need to start derivation before we can make L2 blocks
seq.ActL2PipelineFull(t)

l1Cl := miner.EthClient()
l2Cl := seqEngine.EthClient()
withdrawalsCl := &withdrawals.Client{} // TODO: need a rollup node actor to wrap for output root proof RPC
l2ProofCl := seqEngine.GethClient()

addresses := e2eutils.CollectAddresses(sd, dp)

Expand All @@ -39,7 +55,7 @@ func TestCrossLayerUser(gt *testing.T) {
EthCl: l2Cl,
Signer: types.LatestSigner(sd.L2Cfg.Config),
AddressCorpora: addresses,
Bindings: NewL2Bindings(t, l2Cl, withdrawalsCl),
Bindings: NewL2Bindings(t, l2Cl, l2ProofCl),
}

alice := NewCrossLayerUser(log, dp.Secrets.Alice, rand.New(rand.NewSource(1234)))
Expand Down Expand Up @@ -79,4 +95,50 @@ func TestCrossLayerUser(gt *testing.T) {
}
// Now that the L2 chain adopted the latest L1 block, check that we processed the deposit
alice.ActCheckDepositStatus(true, true)(t)

// regular withdrawal, in new L2 block
alice.ActStartWithdrawal(t)
seq.ActL2StartBlock(t)
seqEngine.ActL2IncludeTx(alice.Address())(t)
seq.ActL2EndBlock(t)
alice.ActCheckStartWithdrawal(true)(t)

// build a L1 block and more L2 blocks,
// to ensure the L2 withdrawal is old enough to be able to get into an output root proposal on L1
miner.ActEmptyBlock(t)
seq.ActL1HeadSignal(t)
seq.ActBuildToL1Head(t)

// submit everything to L1
batcher.ActSubmitAll(t)
// include batch on L1
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(dp.Addresses.Batcher)(t)
miner.ActL1EndBlock(t)

// derive from L1, blocks will now become safe to propose
seq.ActL2PipelineFull(t)

// make proposals until there is nothing left to propose
for proposer.CanPropose(t) {
// propose it to L1
proposer.ActMakeProposalTx(t)
// include proposal on L1
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(dp.Addresses.Proposer)(t)
miner.ActL1EndBlock(t)
// Check proposal was successful
receipt, err := miner.EthClient().TransactionReceipt(t.Ctx(), proposer.LastProposalTx())
require.NoError(t, err)
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "proposal failed")
}

// make the L1 side of the withdrawal tx
alice.ActCompleteWithdrawal(t)
// include completed withdrawal in new L1 block
miner.ActL1StartBlock(12)(t)
miner.ActL1IncludeTx(alice.Address())(t)
miner.ActL1EndBlock(t)
// check withdrawal succeeded
alice.L1.ActCheckReceiptStatusOfLastTx(true)(t)
}
8 changes: 5 additions & 3 deletions op-e2e/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/libp2p/go-libp2p-core/peer"
Expand Down Expand Up @@ -791,12 +792,13 @@ func TestWithdrawals(t *testing.T) {
header, err = l2Verif.HeaderByNumber(ctx, new(big.Int).SetUint64(blockNumber))
require.Nil(t, err)

rpc, err := rpc.Dial(sys.Nodes["verifier"].WSEndpoint())
rpcClient, err := rpc.Dial(sys.Nodes["verifier"].WSEndpoint())
require.Nil(t, err)
l2client := withdrawals.NewClient(rpc)
proofCl := gethclient.New(rpcClient)
receiptCl := ethclient.NewClient(rpcClient)

// Now create withdrawal
params, err := withdrawals.FinalizeWithdrawalParameters(context.Background(), l2client, tx.Hash(), header)
params, err := withdrawals.FinalizeWithdrawalParameters(context.Background(), proofCl, receiptCl, tx.Hash(), header)
require.Nil(t, err)

portal, err := bindings.NewOptimismPortal(predeploys.DevOptimismPortalAddr, l1Client)
Expand Down
34 changes: 8 additions & 26 deletions op-node/withdrawals/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import (
"math/big"
"time"

"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/ethereum/go-ethereum/rpc"

"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
)

var MessagePassedTopic = crypto.Keccak256Hash([]byte("MessagePassed(uint256,address,address,uint256,uint256,bytes,bytes32)"))
Expand Down Expand Up @@ -122,29 +122,11 @@ loop:
}

type ProofClient interface {
TransactionReceipt(context.Context, common.Hash) (*types.Receipt, error)
GetProof(context.Context, common.Address, []string, *big.Int) (*gethclient.AccountResult, error)
}

type ec = *ethclient.Client
type gc = *gethclient.Client

type Client struct {
ec
gc
}

// Ensure that ProofClient and Client interfaces are valid
var _ ProofClient = &Client{}

// NewClient wraps a RPC client with both ethclient and gethclient methods.
// Implements ProofClient
func NewClient(client *rpc.Client) *Client {
return &Client{
ethclient.NewClient(client),
gethclient.New(client),
}

type ReceiptClient interface {
TransactionReceipt(context.Context, common.Hash) (*types.Receipt, error)
}

// FinalizedWithdrawalParameters is the set of parameters to pass to the FinalizedWithdrawal function
Expand All @@ -163,9 +145,9 @@ type FinalizedWithdrawalParameters struct {
// FinalizeWithdrawalParameters queries L2 to generate all withdrawal parameters and proof necessary to finalize an withdrawal on L1.
// The header provided is very important. It should be a block (timestamp) for which there is a submitted output in the L2 Output Oracle
// contract. If not, the withdrawal will fail as it the storage proof cannot be verified if there is no submitted state root.
func FinalizeWithdrawalParameters(ctx context.Context, l2client ProofClient, txHash common.Hash, header *types.Header) (FinalizedWithdrawalParameters, error) {
func FinalizeWithdrawalParameters(ctx context.Context, proofCl ProofClient, l2ReceiptCl ReceiptClient, txHash common.Hash, header *types.Header) (FinalizedWithdrawalParameters, error) {
// Transaction receipt
receipt, err := l2client.TransactionReceipt(ctx, txHash)
receipt, err := l2ReceiptCl.TransactionReceipt(ctx, txHash)
if err != nil {
return FinalizedWithdrawalParameters{}, err
}
Expand All @@ -183,7 +165,7 @@ func FinalizeWithdrawalParameters(ctx context.Context, l2client ProofClient, txH
return FinalizedWithdrawalParameters{}, err
}
slot := StorageSlotOfWithdrawalHash(withdrawalHash)
p, err := l2client.GetProof(ctx, predeploys.L2ToL1MessagePasserAddr, []string{slot.String()}, header.Number)
p, err := proofCl.GetProof(ctx, predeploys.L2ToL1MessagePasserAddr, []string{slot.String()}, header.Number)
if err != nil {
return FinalizedWithdrawalParameters{}, err
}
Expand Down