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
61 changes: 53 additions & 8 deletions op-e2e/interop/interop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ package interop

import (
"context"
"fmt"
"math/big"
"testing"
"time"

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

"github.com/ethereum-optimism/optimism/op-chain-ops/interopgen"
"github.com/ethereum-optimism/optimism/op-e2e/system/helpers"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)

// TestInteropTrivial tests a simple interop scenario
Expand All @@ -20,10 +23,11 @@ import (
// The balance of Bob on Chain A is checked before and after the tx.
// The balance of Bob on Chain B is checked after the tx.
func TestInteropTrivial(t *testing.T) {
genesisTimestamp := uint64(time.Now().Unix() + 3) // start chain 3 seconds from now
recipe := interopgen.InteropDevRecipe{
L1ChainID: 900100,
L2ChainIDs: []uint64{900200, 900201},
GenesisTimestamp: uint64(time.Now().Unix() + 3), // start chain 3 seconds from now
GenesisTimestamp: genesisTimestamp, // start chain 3 seconds from now
}
worldResources := worldResourcePaths{
foundryArtifacts: "../../packages/contracts-bedrock/forge-artifacts",
Expand Down Expand Up @@ -68,7 +72,7 @@ func TestInteropTrivial(t *testing.T) {
)

// check the balance of Bob after the tx
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
ctx, cancel = context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
bobBalance, err = clientA.BalanceAt(ctx, bobAddr, nil)
require.NoError(t, err)
Expand All @@ -85,16 +89,57 @@ func TestInteropTrivial(t *testing.T) {
expectedBalance, _ = big.NewInt(0).SetString("10000000000000000000000000", 10)
require.Equal(t, expectedBalance, bobBalance)

// put some generic log event data down
s2.DeployEmitterContract(chainA, "Alice")
rec := s2.EmitData(chainA, "Alice", "0x1234567890abcdef")

fmt.Println("Result of emitting event:", rec)
rec := s2.EmitData(chainA, "Alice", "emit this!")

s2.DeployEmitterContract(chainB, "Alice")
rec = s2.EmitData(chainB, "Alice", "0x1234567890abcdef")
client := s2.L2GethClient(chainA)
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
block, err := client.BlockByHash(ctx, rec.BlockHash)
require.NoError(t, err)

fmt.Println("Result of emitting event:", rec)
logIndex, ok, err := getStartingLogIndex(client, rec.BlockHash, rec.TxHash)
require.NoError(t, err, "Getting starting log index")
require.True(t, ok, "Transaction not found in block")

// build an identifier to that log event using the receipt
identifier := types.Identifier{
Origin: s2.Address(chainA, "Alice"),
BlockNumber: rec.BlockNumber.Uint64(),
LogIndex: logIndex,
Timestamp: block.Time(),
ChainID: types.ChainIDFromBig(s2.ChainID(chainA)),
}

// indicate the executing message dependency on the log event
s2.ExecuteMessage(chainA, "Alice", identifier, "Alice", []byte("emit this!"))

time.Sleep(60 * time.Second)

}

// getStartingLogIndex returns the block-level index of the first log emitted by the given transaction, and a bool
// indicating whether the transaction was found in the block.
// It does not validate that the given transaction actually has logs.
func getStartingLogIndex(client *ethclient.Client, blockHash common.Hash, txHash common.Hash) (uint64, bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
receipts, err := client.BlockReceipts(ctx, rpc.BlockNumberOrHash{BlockHash: &blockHash})
if err != nil {
return 0, false, err
}

// Count the logs until we find the tx
logCount := uint64(0)
txFound := false
for _, receipt := range receipts {
if receipt.TxHash == txHash {
txFound = true
break
}
logCount += uint64(len(receipt.Logs))
}
return logCount, txFound, nil
}
77 changes: 57 additions & 20 deletions op-e2e/interop/supersystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog"
supervisorConfig "github.com/ethereum-optimism/optimism/op-supervisor/config"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/source/contracts"
supervisorTypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)

// SuperSystem is an interface for the system (collection of connected resources)
Expand All @@ -62,38 +64,41 @@ import (
// for example, interopE2ESystem is the default implementation, but a shim to
// kurtosis or another testing framework could be implemented
type SuperSystem interface {
// get the supervisor
Supervisor() *supervisor.SupervisorService
// get the supervisor client
SupervisorClient() *sources.SupervisorClient
// get the batcher for a network
L2sSystem
UserSystem
ContractSystem
}

type L2sSystem interface {
L2IDs() []string
ChainID(network string) *big.Int
SendL2Tx(network string, username string, applyTxOpts helpers.TxOptsFn) *types.Receipt
Batcher(network string) *bss.BatcherService
// get the proposer for a network
Proposer(network string) *l2os.ProposerService
// get the opnode for a network
OpNode(network string) *opnode.Opnode
// get the geth instance for a network
L2Geth(network string) *geth.GethInstance
// get the L2 geth client for a network
L2GethClient(network string) *ethclient.Client
// get the secret for a network and role
L2OperatorKey(network string, role devkeys.ChainOperatorRole) ecdsa.PrivateKey
// get the list of network IDs
L2IDs() []string
// register a username to an account on all L2s
}

type UserSystem interface {
AddUser(username string)
// get the user key for a user on an L2
UserKey(id, username string) ecdsa.PrivateKey
// send a transaction on an L2 on the given network, from the given user
SendL2Tx(network string, username string, applyTxOpts helpers.TxOptsFn) *types.Receipt
// get the address for a user on an L2
Address(network string, username string) common.Address
// Deploy the Emitter Contract, which emits Event Logs
}

// ContractSystem is an interface for interacting with contracts of the system
type ContractSystem interface {
// Contract returns the contract for the network and contract name
Contract(network string, contractName string) interface{}
// DeployEmitterContract deploys an emitter contract on the network
DeployEmitterContract(network string, username string) common.Address
// Use the Emitter Contract to emit an Event Log
// EmitData emits data on the network, using the emitter contract
EmitData(network string, username string, data string) *types.Receipt
// Access a contract on a network by name
Contract(network string, contractName string) interface{}
// ExecuteMessage executes a message on the network using the CrossL2Inbox contract
ExecuteMessage(network string, sender string, identifier supervisorTypes.Identifier, address string, payload []byte) *types.Receipt
}

// NewSuperSystem creates a new SuperSystem from a recipe. It creates an interopE2ESystem.
Expand Down Expand Up @@ -410,7 +415,9 @@ func (s *interopE2ESystem) newL2(id string, l2Out *interopgen.L2Output) l2Set {
batcher: batcher,
operatorKeys: operatorKeys,
userKeys: make(map[string]ecdsa.PrivateKey),
contracts: make(map[string]interface{}),
contracts: map[string]interface{}{
"CrossL2Inbox": contracts.NewCrossL2Inbox(),
},
}
}

Expand Down Expand Up @@ -592,6 +599,13 @@ func (s *interopE2ESystem) L2IDs() []string {
return ids
}

// ChainID returns the chain ID for an L2
func (s *interopE2ESystem) ChainID(id string) *big.Int {
l2, ok := s.l2s[id]
require.True(s.t, ok, "no L2 found for id %s", id)
return l2.chainID
}

// SendL2Tx sends an L2 transaction to the L2 with the given ID.
// it acts as a wrapper around op-e2e.SendL2TxWithID
// and uses the L2's chain ID, username key, and geth client.
Expand Down Expand Up @@ -633,6 +647,29 @@ func (s *interopE2ESystem) DeployEmitterContract(
return address
}

func (s *interopE2ESystem) ExecuteMessage(
// id is the chain this transaction will occur on
id string,
// sender is the user who is sending the transaction
sender string,
// identifier, address, payload are arguments to the ExecuteMessage function
identifier supervisorTypes.Identifier,
address string,
payload []byte,
) *types.Receipt {
addr := s.Address(id, address)
contract := s.l2s[id].contracts["CrossL2Inbox"].(*contracts.CrossL2Inbox)
candidate, err := contract.ExecuteMessage(identifier, addr, payload)
require.NoError(s.t, err)
// rough estimate of gas cost for the transaction, because the candidate doesn't have one
gasEstimate := 24000 + len(candidate.TxData)*10
return s.SendL2Tx(id, sender, func(opts *helpers.TxOpts) {
opts.Gas = uint64(gasEstimate)
opts.ToAddr = candidate.To
opts.Data = candidate.TxData
})
}

func (s *interopE2ESystem) EmitData(
id string,
sender string,
Expand Down
15 changes: 12 additions & 3 deletions op-service/sources/supervisor_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"

"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/eth"
Expand Down Expand Up @@ -68,7 +67,12 @@ func (cl *SupervisorClient) AddL2RPC(

func (cl *SupervisorClient) UnsafeView(ctx context.Context, chainID types.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) {
var result types.ReferenceView
err := cl.client.CallContext(ctx, &result, "supervisor_unsafeView", (*hexutil.U256)(&chainID), unsafe)
err := cl.client.CallContext(
ctx,
&result,
"supervisor_unsafeView",
chainID,
unsafe)
if err != nil {
return types.ReferenceView{}, fmt.Errorf("failed to share unsafe block view %s (chain %s): %w", unsafe, chainID, err)
}
Expand All @@ -77,7 +81,12 @@ func (cl *SupervisorClient) UnsafeView(ctx context.Context, chainID types.ChainI

func (cl *SupervisorClient) SafeView(ctx context.Context, chainID types.ChainID, safe types.ReferenceView) (types.ReferenceView, error) {
var result types.ReferenceView
err := cl.client.CallContext(ctx, &result, "supervisor_safeView", (*hexutil.U256)(&chainID), safe)
err := cl.client.CallContext(
ctx,
&result,
"supervisor_safeView",
chainID,
safe)
if err != nil {
return types.ReferenceView{}, fmt.Errorf("failed to share safe block view %s (chain %s): %w", safe, chainID, err)
}
Expand Down
Loading