Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(admin): getL1ToL2MessageByTxHash(l1DepositTxHash) #280

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
55 changes: 53 additions & 2 deletions admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/ethereum-optimism/supersim/config"
"github.com/ethereum-optimism/supersim/interop"
"github.com/ethereum-optimism/supersim/opsimulator"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
Expand All @@ -26,6 +27,7 @@ type AdminServer struct {

networkConfig *config.NetworkConfig
l2ToL2MsgIndexer *interop.L2ToL2MessageIndexer
l1DepositStore *opsimulator.L1DepositStoreManager

port uint64
}
Expand All @@ -34,6 +36,7 @@ type RPCMethods struct {
log log.Logger
networkConfig *config.NetworkConfig
l2ToL2MsgIndexer *interop.L2ToL2MessageIndexer
l1DepositStore *opsimulator.L1DepositStoreManager
}

type JSONRPCError struct {
Expand All @@ -50,6 +53,17 @@ type JSONL2ToL2Message struct {
Message hexutil.Bytes `json:"Message"`
}

type JSONDepositTx struct {
SourceHash common.Hash `json:"SourceHash"`
From common.Address `json:"From"`
To *common.Address `json:"To"`
Mint *big.Int `json:"Mint"`
Value *big.Int `json:"Value"`
Gas uint64 `json:"Gas"`
IsSystemTransaction bool `json:"IsSystemTransaction"`
Data hexutil.Bytes `json:"Data"`
}

func (e *JSONRPCError) Error() string {
return e.Message
}
Expand All @@ -58,9 +72,9 @@ func (err *JSONRPCError) ErrorCode() int {
return err.Code
}

func NewAdminServer(log log.Logger, port uint64, networkConfig *config.NetworkConfig, indexer *interop.L2ToL2MessageIndexer) *AdminServer {
func NewAdminServer(log log.Logger, port uint64, networkConfig *config.NetworkConfig, indexer *interop.L2ToL2MessageIndexer, l1DepositStore *opsimulator.L1DepositStoreManager) *AdminServer {

adminServer := &AdminServer{log: log, port: port, networkConfig: networkConfig}
adminServer := &AdminServer{log: log, port: port, networkConfig: networkConfig, l1DepositStore: l1DepositStore}

if networkConfig.InteropEnabled && indexer != nil {
adminServer.l2ToL2MsgIndexer = indexer
Expand Down Expand Up @@ -140,6 +154,7 @@ func (s *AdminServer) setupRouter() *gin.Engine {
log: s.log,
networkConfig: s.networkConfig,
l2ToL2MsgIndexer: s.l2ToL2MsgIndexer,
l1DepositStore: s.l1DepositStore,
}

if err := rpcServer.RegisterName("admin", rpcMethods); err != nil {
Expand Down Expand Up @@ -223,3 +238,39 @@ func (m *RPCMethods) GetL2ToL2MessageByMsgHash(args *common.Hash) (*JSONL2ToL2Me
Message: msg.Message,
}, nil
}

func (m *RPCMethods) GetL1ToL2MessageByTxnHash(args *common.Hash) (*JSONDepositTx, error) {
if m.l1DepositStore == nil {
return nil, &JSONRPCError{
Code: -32601,
Message: "L1DepositStoreManager is not initialized.",
}
}

if (args == nil || args == &common.Hash{}) {
return nil, &JSONRPCError{
Code: -32602,
Message: "Valid msg hash not provided",
}
}

storeEntry, err := m.l1DepositStore.Get(*args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should query the indexer not the store directly.


if err != nil {
return nil, &JSONRPCError{
Code: -32603,
Message: fmt.Sprintf("Failed to get message: %v", err),
}
}

return &JSONDepositTx{
SourceHash: storeEntry.SourceHash,
From: storeEntry.From,
To: storeEntry.To,
Mint: storeEntry.Mint,
Value: storeEntry.Value,
Gas: storeEntry.Gas,
IsSystemTransaction: storeEntry.IsSystemTransaction,
Data: storeEntry.Data,
}, nil
}
4 changes: 2 additions & 2 deletions admin/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestAdminServerBasicFunctionality(t *testing.T) {
testlog := testlog.Logger(t, log.LevelInfo)

ctx, cancel := context.WithCancel(context.Background())
adminServer := NewAdminServer(testlog, 0, &networkConfig, nil)
adminServer := NewAdminServer(testlog, 0, &networkConfig, nil, nil)
t.Cleanup(func() { cancel() })

require.NoError(t, adminServer.Start(ctx))
Expand All @@ -46,7 +46,7 @@ func TestGetL1AddressesRPC(t *testing.T) {
testlog := testlog.Logger(t, log.LevelInfo)

ctx, cancel := context.WithCancel(context.Background())
adminServer := NewAdminServer(testlog, 0, &networkConfig, nil)
adminServer := NewAdminServer(testlog, 0, &networkConfig, nil, nil)
t.Cleanup(func() { cancel() })

require.NoError(t, adminServer.Start(ctx))
Expand Down
3 changes: 2 additions & 1 deletion opsimulator/deposits.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type LogSubscriber interface {
}

// transforms Deposit event logs into DepositTx
func SubscribeDepositTx(ctx context.Context, logSub LogSubscriber, depositContractAddr common.Address, ch chan<- *types.DepositTx) (ethereum.Subscription, error) {
func SubscribeDepositTx(ctx context.Context, logSub LogSubscriber, depositContractAddr common.Address, ch chan<- *types.DepositTx, chLog chan<- *types.Log) (ethereum.Subscription, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high level it would be cleaner to just pass a single channel here.

if you need other info from the depositTx like some of the log data, you can create a wrapper that includes the deposit tx along with the log

logCh := make(chan types.Log)
filterQuery := ethereum.FilterQuery{Addresses: []common.Address{depositContractAddr}, Topics: [][]common.Hash{{derive.DepositEventABIHash}}}
logSubscription, err := logSub.SubscribeFilterLogs(ctx, filterQuery, logCh)
Expand All @@ -61,6 +61,7 @@ func SubscribeDepositTx(ctx context.Context, logSub LogSubscriber, depositContra
continue
}
ch <- dep
chLog <- &log
case err := <-logErrCh:
errCh <- fmt.Errorf("log subscription error: %w", err)
case <-ctx.Done():
Expand Down
3 changes: 2 additions & 1 deletion opsimulator/deposits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ func TestSubscribeDepositTx(t *testing.T) {
ctx := context.Background()

depositTxCh := make(chan *types.DepositTx, len(mockDepositTxs))
depositLogCh := make(chan *types.Log, len(mockDepositTxs))

sub, err := SubscribeDepositTx(ctx, &chain, common.HexToAddress(""), depositTxCh)
sub, err := SubscribeDepositTx(ctx, &chain, common.HexToAddress(""), depositTxCh, depositLogCh)
if err != nil {
require.NoError(t, err)
}
Expand Down
13 changes: 11 additions & 2 deletions opsimulator/opsimulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ type OpSimulator struct {
ethClient *ethclient.Client

stopped atomic.Bool

l1DepositStoreManager *L1DepositStoreManager
}

// OpSimulator wraps around the l2 chain. By embedding `Chain`, it also implements the same inteface
func New(log log.Logger, closeApp context.CancelCauseFunc, port uint64, host string, l1Chain, l2Chain config.Chain, peers map[uint64]config.Chain, interopDelay uint64) *OpSimulator {
func New(log log.Logger, closeApp context.CancelCauseFunc, port uint64, host string, l1Chain, l2Chain config.Chain, peers map[uint64]config.Chain, interopDelay uint64, depositStoreManager *L1DepositStoreManager) *OpSimulator {
bgTasksCtx, bgTasksCancel := context.WithCancel(context.Background())

crossL2Inbox, err := bindings.NewCrossL2Inbox(predeploys.CrossL2InboxAddr, l2Chain.EthClient())
Expand All @@ -88,6 +90,8 @@ func New(log log.Logger, closeApp context.CancelCauseFunc, port uint64, host str
},
},

l1DepositStoreManager: depositStoreManager,

peers: peers,
}
}
Expand Down Expand Up @@ -148,8 +152,9 @@ func (opSim *OpSimulator) startBackgroundTasks() {
// Relay deposit tx from L1 to L2
opSim.bgTasks.Go(func() error {
depositTxCh := make(chan *types.DepositTx)
l1DepositTxnLogCh := make(chan *types.Log)
portalAddress := common.Address(opSim.Config().L2Config.L1Addresses.OptimismPortalProxy)
sub, err := SubscribeDepositTx(context.Background(), opSim.l1Chain.EthClient(), portalAddress, depositTxCh)
sub, err := SubscribeDepositTx(context.Background(), opSim.l1Chain.EthClient(), portalAddress, depositTxCh, l1DepositTxnLogCh)
Copy link
Contributor

@jakim929 jakim929 Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to the indexer/relayer design for l2 to l2 messages, it will be more maintainable to

have the indexer have a start function and run separately from the opsimulator. the op simulator then "listens" to the indexer to get the stream of deposit tx

all of the setting new logs logic should happen inside the deposit indexer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jakim929 just clarifying the new indexer will be subscribing to deposit tx and storing, while the opsim would just be used to start the L1ToL2Indexer service correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while indexer listens for new tranction a transaction event should also be sent from indexer to opsim to SendTransaction to done while the indexer store ref to the initial depTxn sub message

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or another way could be both opsim and indexer can both sub to deposit txn, the op sim executes transaction while the indexer just stores ref?

if err != nil {
return fmt.Errorf("failed to subscribe to deposit tx: %w", err)
}
Expand All @@ -160,6 +165,7 @@ func (opSim *OpSimulator) startBackgroundTasks() {
select {
case dep := <-depositTxCh:
depTx := types.NewTx(dep)
l1Log := <-l1DepositTxnLogCh
opSim.log.Debug("observed deposit event on L1", "hash", depTx.Hash().String())

clnt := opSim.Chain.EthClient()
Expand All @@ -168,6 +174,9 @@ func (opSim *OpSimulator) startBackgroundTasks() {
}

opSim.log.Info("OptimismPortal#depositTransaction", "l2TxHash", depTx.Hash().String())
if err := opSim.l1DepositStoreManager.Set(l1Log.TxHash, dep); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a single l1 transaction can have multiple deposit tx inside it so it cannot be used as the index. instead the deposit tx's own transaction hash might be a better one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it makes sense updating this

opSim.log.Error("failed to store deposit tx to chain: %w", "chain.id", chainId, "err", err)
}

case <-opSim.bgTasksCtx.Done():
sub.Unsubscribe()
Expand Down
63 changes: 63 additions & 0 deletions opsimulator/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package opsimulator

import (
"fmt"
"sync"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)

type L1DepositStore struct {
entryByHash map[common.Hash]*types.DepositTx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some high level thoughts

  1. entryByHash should be indexed by something that globally & uniquely identifies a depositTX. the l1 txhash is insufficient because
  2. you should prob maybe store more than just the deposit tx here since the indexer likely wants more info than that (ie. the log where this was emitted for instance)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update:

  1. using the source hash as the key value, since it uniquely identifies the source of the deposit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. updated the message to contain both the depositTxn and the initiating log in the form of a struct

mu sync.RWMutex
}

type L1DepositStoreManager struct {
store *L1DepositStore
}

func NewL1DepositStore() *L1DepositStore {
return &L1DepositStore{
entryByHash: make(map[common.Hash]*types.DepositTx),
}
}

func NewL1DepositStoreManager() *L1DepositStoreManager {
return &L1DepositStoreManager{
store: NewL1DepositStore(),
}
}

func (s *L1DepositStore) Set(txnHash common.Hash, entry *types.DepositTx) error {
s.mu.Lock()
defer s.mu.Unlock()

s.entryByHash[txnHash] = entry
return nil
}

func (s *L1DepositStore) Get(txnHash common.Hash) (*types.DepositTx, error) {
s.mu.RLock()
defer s.mu.RUnlock()

entry, exists := s.entryByHash[txnHash]

if !exists {
return nil, fmt.Errorf("Deposit txn not found")
}

return entry, nil
}

func (s *L1DepositStoreManager) Get(txnHash common.Hash) (*types.DepositTx, error) {
return s.store.Get(txnHash)
}

func (s *L1DepositStoreManager) Set(txnHash common.Hash, entry *types.DepositTx) error {
if err := s.store.Set(txnHash, entry); err != nil {
return fmt.Errorf("failed to store message: %w", err)
}

return nil
}
28 changes: 28 additions & 0 deletions opsimulator/store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package opsimulator

import (
"math/rand"
"testing"

optestutils "github.com/ethereum-optimism/optimism/op-service/testutils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/assert"
)

func TestL1DepositStore_SetAndGet(t *testing.T) {
sm := NewL1DepositStoreManager()

rng := rand.New(rand.NewSource(int64(0)))
sourceHash := common.Hash{}
depInput := optestutils.GenerateDeposit(sourceHash, rng)
depTx := types.NewTx(depInput)
txnHash := depTx.Hash()

err := sm.store.Set(txnHash, depInput)
assert.NoError(t, err, "expect no error while store deposit txn ref")

retrievedEntry, err := sm.store.Get(txnHash)
assert.NoError(t, err, "expected no error when getting entry from store")
assert.Equal(t, depInput, retrievedEntry, "expected retrieved entry to equal stored entry")
}
6 changes: 4 additions & 2 deletions orchestrator/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@ func NewOrchestrator(log log.Logger, closeApp context.CancelCauseFunc, networkCo
l2Anvils[cfg.ChainID] = l2Anvil
}

depositStoreMngr := opsimulator.NewL1DepositStoreManager()

// Sping up OpSim to fornt the L2 instances
for i := range networkConfig.L2Configs {
cfg := networkConfig.L2Configs[i]

l2OpSims[cfg.ChainID] = opsimulator.New(log, closeApp, nextL2Port, cfg.Host, l1Anvil, l2Anvils[cfg.ChainID], l2Anvils, networkConfig.InteropDelay)
l2OpSims[cfg.ChainID] = opsimulator.New(log, closeApp, nextL2Port, cfg.Host, l1Anvil, l2Anvils[cfg.ChainID], l2Anvils, networkConfig.InteropDelay, depositStoreMngr)

// only increment expected port if it has been specified
if nextL2Port > 0 {
Expand All @@ -71,7 +73,7 @@ func NewOrchestrator(log log.Logger, closeApp context.CancelCauseFunc, networkCo
}
}

a := admin.NewAdminServer(log, adminPort, networkConfig, o.l2ToL2MsgIndexer)
a := admin.NewAdminServer(log, adminPort, networkConfig, o.l2ToL2MsgIndexer, depositStoreMngr)

o.AdminServer = a

Expand Down
Loading