Commit 19eb157f authored by Hamdi Allam's avatar Hamdi Allam

Legacy block processor

parent 7662a3b3
......@@ -37,7 +37,6 @@ func Grouped(start, end *big.Int, size uint64) []Range {
if end.Cmp(start) < 0 || size == 0 {
return nil
}
bigMaxDiff := big.NewInt(int64(size - 1))
groups := []Range{}
......
......@@ -7,7 +7,7 @@ import (
"github.com/BurntSushi/toml"
"github.com/ethereum/go-ethereum/common"
geth_log "github.com/ethereum/go-ethereum/log"
log "github.com/ethereum/go-ethereum/log"
)
const (
......@@ -34,6 +34,9 @@ type L1Contracts struct {
L1CrossDomainMessengerProxy common.Address `toml:"l1-cross-domain-messenger"`
L1StandardBridgeProxy common.Address `toml:"l1-standard-bridge"`
// Pre-Bedrock Legacy Contracts
LegacyCanonicalTransactionChain common.Address `toml:"l1-canonical-transaction-chain"`
// Some more contracts -- L1ERC721Bridge, ProxyAdmin, SystemConfig, etc
// Ignore the auxiliary contracts?
......@@ -69,6 +72,10 @@ type ChainConfig struct {
L1Contracts L1Contracts `toml:"l1-contracts"`
L1StartingHeight uint `toml:"l1-starting-height"`
// Bedrock starting heights only applicable for OP-Mainnet & OP-Goerli
L1BedrockStartingHeight uint `toml:"-"`
L2BedrockStartingHeight uint `toml:"-"`
// These configuration options will be removed once
// native reorg handling is implemented
L1ConfirmationDepth uint `toml:"l1-confirmation-depth"`
......@@ -103,8 +110,8 @@ type ServerConfig struct {
}
// LoadConfig loads the `indexer.toml` config file from a given path
func LoadConfig(logger geth_log.Logger, path string) (Config, error) {
logger.Debug("loading config", "path", path)
func LoadConfig(log log.Logger, path string) (Config, error) {
log.Debug("loading config", "path", path)
var conf Config
data, err := os.ReadFile(path)
......@@ -113,9 +120,9 @@ func LoadConfig(logger geth_log.Logger, path string) (Config, error) {
}
data = []byte(os.ExpandEnv(string(data)))
logger.Debug("parsed config file", "data", string(data))
log.Debug("parsed config file", "data", string(data))
if _, err := toml.Decode(string(data), &conf); err != nil {
logger.Info("failed to decode config file", "err", err)
log.Info("failed to decode config file", "err", err)
return conf, err
}
......@@ -124,31 +131,34 @@ func LoadConfig(logger geth_log.Logger, path string) (Config, error) {
if !ok {
return conf, fmt.Errorf("unknown preset: %d", conf.Chain.Preset)
}
conf.Chain.L1Contracts = knownPreset.L1Contracts
conf.Chain.L1StartingHeight = knownPreset.L1StartingHeight
conf.Chain.L1BedrockStartingHeight = knownPreset.L1BedrockStartingHeight
conf.Chain.L2BedrockStartingHeight = knownPreset.L1BedrockStartingHeight
}
// Set polling defaults if not set
if conf.Chain.L1PollingInterval == 0 {
logger.Info("setting default L1 polling interval", "interval", defaultLoopInterval)
log.Info("setting default L1 polling interval", "interval", defaultLoopInterval)
conf.Chain.L1PollingInterval = defaultLoopInterval
}
if conf.Chain.L2PollingInterval == 0 {
logger.Info("setting default L2 polling interval", "interval", defaultLoopInterval)
log.Info("setting default L2 polling interval", "interval", defaultLoopInterval)
conf.Chain.L2PollingInterval = defaultLoopInterval
}
if conf.Chain.L1HeaderBufferSize == 0 {
logger.Info("setting default L1 header buffer", "size", defaultHeaderBufferSize)
log.Info("setting default L1 header buffer", "size", defaultHeaderBufferSize)
conf.Chain.L1HeaderBufferSize = defaultHeaderBufferSize
}
if conf.Chain.L2HeaderBufferSize == 0 {
logger.Info("setting default L2 header buffer", "size", defaultHeaderBufferSize)
log.Info("setting default L2 header buffer", "size", defaultHeaderBufferSize)
conf.Chain.L2HeaderBufferSize = defaultHeaderBufferSize
}
logger.Info("loaded config")
log.Info("loaded config")
return conf, nil
}
......@@ -14,9 +14,11 @@ var presetConfigs = map[int]ChainConfig{
L2OutputOracleProxy: common.HexToAddress("0xdfe97868233d1aa22e815a266982f2cf17685a27"),
L1CrossDomainMessengerProxy: common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1"),
L1StandardBridgeProxy: common.HexToAddress("0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1"),
// LegacyCanonicalTransactionChain: common.HexToAddress("0x5e4e65926ba27467555eb562121fac00d24e9dd2"),
LegacyCanonicalTransactionChain: common.HexToAddress("0x5e4e65926ba27467555eb562121fac00d24e9dd2"),
},
L1StartingHeight: 13596466,
L1BedrockStartingHeight: 17422590,
L2BedrockStartingHeight: 105235063,
},
// OP Goerli
420: {
......@@ -25,8 +27,11 @@ var presetConfigs = map[int]ChainConfig{
L2OutputOracleProxy: common.HexToAddress("0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0"),
L1CrossDomainMessengerProxy: common.HexToAddress("0x5086d1eEF304eb5284A0f6720f79403b4e9bE294"),
L1StandardBridgeProxy: common.HexToAddress("0x636Af16bf2f682dD3109e60102b8E1A089FedAa8"),
LegacyCanonicalTransactionChain: common.HexToAddress("0x2ebA8c4EfDB39A8Cd8f9eD65c50ec079f7CEBD81"),
},
L1StartingHeight: 7017096,
L1BedrockStartingHeight: 8300214,
L2BedrockStartingHeight: 4061224,
},
// Base Mainnet
8453: {
......
......@@ -29,9 +29,10 @@ type ETL struct {
headerBufferSize uint64
headerTraversal *node.HeaderTraversal
ethClient node.EthClient
contracts []common.Address
etlBatches chan ETLBatch
EthClient node.EthClient
}
type ETLBatch struct {
......@@ -104,7 +105,7 @@ func (etl *ETL) processBatch(headers []types.Header) error {
}
headersWithLog := make(map[common.Hash]bool, len(headers))
logs, err := etl.ethClient.FilterLogs(ethereum.FilterQuery{FromBlock: firstHeader.Number, ToBlock: lastHeader.Number, Addresses: etl.contracts})
logs, err := etl.EthClient.FilterLogs(ethereum.FilterQuery{FromBlock: firstHeader.Number, ToBlock: lastHeader.Number, Addresses: etl.contracts})
if err != nil {
batchLog.Info("unable to extract logs", "err", err)
return err
......
......@@ -66,9 +66,10 @@ func NewL1ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli
log: log,
metrics: metrics,
headerTraversal: node.NewHeaderTraversal(client, fromHeader, cfg.ConfirmationDepth),
ethClient: client,
contracts: cSlice,
etlBatches: etlBatches,
EthClient: client,
}
return &L1ETL{ETL: etl, db: db, mu: new(sync.Mutex)}, nil
......
......@@ -50,9 +50,10 @@ func NewL2ETL(cfg Config, log log.Logger, db *database.DB, metrics Metricer, cli
log: log,
metrics: metrics,
headerTraversal: node.NewHeaderTraversal(client, fromHeader, cfg.ConfirmationDepth),
ethClient: client,
contracts: l2Contracts,
etlBatches: etlBatches,
EthClient: client,
}
return &L2ETL{ETL: etl, db: db}, nil
......
......@@ -2,7 +2,6 @@
# Can configure them manually or use a preset l2 ChainId for known chains including OP Mainnet, OP Goerli, Base, Base Goerli, Zora, and Zora goerli
[chain]
# OP Goerli
preset = $INDEXER_CHAIN_PRESET
# L1 Config
......
......@@ -29,7 +29,7 @@ CREATE TABLE IF NOT EXISTS l2_block_headers (
hash VARCHAR PRIMARY KEY,
parent_hash VARCHAR NOT NULL UNIQUE,
number UINT256 NOT NULL UNIQUE,
timestamp INTEGER NOT NULL UNIQUE CHECK (timestamp > 0),
timestamp INTEGER NOT NULL,
-- Raw Data
rlp_bytes VARCHAR NOT NULL
......
......@@ -29,6 +29,8 @@ type EthClient interface {
BlockHeaderByHash(common.Hash) (*types.Header, error)
BlockHeadersByRange(*big.Int, *big.Int) ([]types.Header, error)
TxByHash(common.Hash) (*types.Transaction, error)
StorageHash(common.Address, *big.Int) (common.Hash, error)
FilterLogs(ethereum.FilterQuery) ([]types.Log, error)
}
......@@ -147,6 +149,23 @@ func (c *client) BlockHeadersByRange(startHeight, endHeight *big.Int) ([]types.H
return headers, nil
}
func (c *client) TxByHash(hash common.Hash) (*types.Transaction, error) {
ctxwt, cancel := context.WithTimeout(context.Background(), defaultRequestTimeout)
defer cancel()
var tx *types.Transaction
err := c.rpc.CallContext(ctxwt, &tx, "eth_getTransactionByHash", hash)
if err != nil {
return nil, err
} else if tx == nil {
return nil, ethereum.NotFound
}
// NOTE: why does ethclient do something different here
return tx, nil
}
// StorageHash returns the sha3 of the storage root for the specified account
func (c *client) StorageHash(address common.Address, blockNumber *big.Int) (common.Hash, error) {
ctxwt, cancel := context.WithTimeout(context.Background(), defaultRequestTimeout)
......
......@@ -124,15 +124,77 @@ func (b *BridgeProcessor) Start(ctx context.Context) error {
fromL2Height = new(big.Int).Add(b.LatestL2Header.Number, bigint.One)
}
l1BedrockStartingHeight := big.NewInt(int64(b.chainConfig.L1BedrockStartingHeight))
l2BedrockStartingHeight := big.NewInt(int64(b.chainConfig.L2BedrockStartingHeight))
batchLog := b.log.New("epoch_start_number", fromL1Height, "epoch_end_number", toL1Height)
batchLog.Info("unobserved epochs")
err = b.db.Transaction(func(tx *database.DB) error {
l1BridgeLog := b.log.New("bridge", "l1")
l2BridgeLog := b.log.New("bridge", "l2")
// In the event where we have a large number of un-observed blocks, group the block range
// on the order of 10k blocks at a time. If this turns out to be a bottleneck, we can
// parallelize these operations for significant improvements as well
l1BridgeLog := b.log.New("bridge", "l1")
l2BridgeLog := b.log.New("bridge", "l2")
// FOR OP-MAINNET, OP-GOERLI ONLY! Specially handle the existence of pre-bedrock blocks
// - Since this processor indexes bridge events on available epochs between L1 & L2, we
// can detect when we've switched into bedrock state by comparing the l1 bedrock starting
// height to the range of L1 blocks we are indexing.
if l1BedrockStartingHeight.Cmp(fromL1Height) > 0 {
legacyFromL1Height, legacyToL1Height := fromL1Height, toL1Height
legacyFromL2Height, legacyToL2Height := fromL2Height, toL2Height
if l1BedrockStartingHeight.Cmp(toL1Height) <= 0 {
legacyToL1Height = new(big.Int).Sub(l1BedrockStartingHeight, big.NewInt(1))
legacyToL1Height = new(big.Int).Sub(l2BedrockStartingHeight, big.NewInt(1))
}
l1BridgeLog := l1BridgeLog.New("mode", "legacy")
l2BridgeLog := l2BridgeLog.New("mode", "legacy")
l1BlockGroups := bigint.Grouped(legacyFromL1Height, legacyToL1Height, 10_000)
l2BlockGroups := bigint.Grouped(legacyFromL2Height, legacyToL2Height, 10_000)
// Now that all initiated events have been indexed, it is ensured that all finalization can find their counterpart.
for _, group := range l1BlockGroups {
log := l1BridgeLog.New("from_l1_block_number", group.Start, "to_l1_block_number", group.End)
log.Info("scanning for initiated bridge events")
if err := bridge.LegacyL1ProcessInitiatedBridgeEvents(log, tx, b.chainConfig, group.Start, group.End); err != nil {
return err
}
}
for _, group := range l2BlockGroups {
log := l2BridgeLog.New("from_l2_block_number", group.Start, "to_l2_block_number", group.End)
log.Info("scanning for initiated bridge events")
if err := bridge.LegacyL2ProcessInitiatedBridgeEvents(log, tx, group.Start, group.End); err != nil {
return err
}
}
// Now that all initiated events have been indexed, it is ensured that all finalization can find their counterpart.
for _, group := range l1BlockGroups {
log := l1BridgeLog.New("from_l1_block_number", group.Start, "to_l1_block_number", group.End)
log.Info("scanning for finalized bridge events")
if err := bridge.LegacyL1ProcessFinalizedBridgeEvents(log, tx, b.l1Etl.EthClient, b.chainConfig, group.Start, group.End); err != nil {
return err
}
}
for _, group := range l2BlockGroups {
log := l2BridgeLog.New("from_l2_block_number", group.Start, "to_l2_block_number", group.End)
log.Info("scanning for finalized bridge events")
if err := bridge.LegacyL2ProcessFinalizedBridgeEvents(log, tx, group.Start, group.End); err != nil {
return err
}
}
if legacyToL1Height.Cmp(toL1Height) == 0 {
// a-ok! entire batch was legacy blocks
return nil
}
batchLog.Info("detected switch to bedrock", "l1_bedrock_starting_height", l1BedrockStartingHeight, "l2_bedrock_starting_height", l2BedrockStartingHeight)
fromL1Height = l1BedrockStartingHeight
fromL2Height = l2BedrockStartingHeight
}
l1BlockGroups := bigint.Grouped(fromL1Height, toL1Height, 10_000)
l2BlockGroups := bigint.Grouped(fromL2Height, toL2Height, 10_000)
......@@ -181,4 +243,5 @@ func (b *BridgeProcessor) Start(ctx context.Context) error {
b.LatestL2Header = latestEpoch.L2BlockHeader.RLPHeader.Header()
}
}
}
package bridge
import (
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/indexer/processors/contracts"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
)
// Legacy Bridge Initiation
func LegacyL1ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, chainConfig config.ChainConfig, fromHeight *big.Int, toHeight *big.Int) error {
// (1) CanonicalTransactionChain
ctcTxDepositEvents, err := contracts.LegacyCTCDepositEvents(chainConfig.L1Contracts.LegacyCanonicalTransactionChain, db, fromHeight, toHeight)
if err != nil {
return err
}
ctcTxDeposits := make(map[logKey]*contracts.LegacyCTCDepositEvent, len(ctcTxDepositEvents))
transactionDeposits := make([]database.L1TransactionDeposit, len(ctcTxDepositEvents))
for i := range ctcTxDepositEvents {
deposit := ctcTxDepositEvents[i]
ctcTxDeposits[logKey{deposit.Event.BlockHash, deposit.Event.LogIndex}] = &deposit
transactionDeposits[i] = database.L1TransactionDeposit{
SourceHash: deposit.TxHash,
L2TransactionHash: deposit.TxHash, // TODO: check that this is right
InitiatedL1EventGUID: deposit.Event.GUID,
GasLimit: deposit.GasLimit,
Tx: deposit.Tx,
}
}
if len(ctcTxDepositEvents) > 0 {
log.Info("detected legacy transaction deposits", "size", len(transactionDeposits))
if err := db.BridgeTransactions.StoreL1TransactionDeposits(transactionDeposits); err != nil {
return err
}
}
// (2) L1CrossDomainMessenger
crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l1", chainConfig.L1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
sentMessages := make(map[logKey]*contracts.CrossDomainMessengerSentMessageEvent, len(crossDomainSentMessages))
l1BridgeMessages := make([]database.L1BridgeMessage, len(crossDomainSentMessages))
for i := range crossDomainSentMessages {
sentMessage := crossDomainSentMessages[i]
sentMessages[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex}] = &sentMessage
// extract the deposit hash from the previous TransactionDepositedEvent
ctcTxDeposit, ok := ctcTxDeposits[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex - 1}]
if !ok {
log.Error("missing transaction deposit for cross domain message", "tx_hash", sentMessage.Event.TransactionHash.String())
return fmt.Errorf("missing preceding TransactionEnqueued for SentMessage event. tx_hash = %s", sentMessage.Event.TransactionHash.String())
}
l1BridgeMessages[i] = database.L1BridgeMessage{TransactionSourceHash: ctcTxDeposit.TxHash, BridgeMessage: sentMessage.BridgeMessage}
}
if len(l1BridgeMessages) > 0 {
log.Info("detected legacy L1CrossDomainMessenger messages", "size", len(l1BridgeMessages))
if err := db.BridgeMessages.StoreL1BridgeMessages(l1BridgeMessages); err != nil {
return err
}
}
// (3) L1StandardBridge
initiatedBridges, err := contracts.L1StandardBridgeLegacyDepositInitiatedEvents(chainConfig.L1Contracts.L1StandardBridgeProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
l1BridgeDeposits := make([]database.L1BridgeDeposit, len(initiatedBridges))
for i := range initiatedBridges {
initiatedBridge := initiatedBridges[i]
// extract the cross domain message hash & deposit source hash from the following events
// Unlike bedrock, the bridge events are emitted AFTER sending the cross domain message
// - Event Flow: TransactionEnqueued -> SentMessage -> DepositInitiated
sentMessage, ok := sentMessages[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex - 1}]
if !ok {
log.Error("missing cross domain message for bridge transfer", "tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected SentMessage preceding TransactionEnqeueud event. tx_hash = %s", initiatedBridge.Event.TransactionHash.String())
}
ctcTxDeposit, ok := ctcTxDeposits[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex - 2}]
if !ok {
log.Error("missing transaction deposit for bridge transfer", "tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected TransactionEnqueued preceding BridgeInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash.String())
}
initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash
l1BridgeDeposits[i] = database.L1BridgeDeposit{
TransactionSourceHash: ctcTxDeposit.TxHash,
BridgeTransfer: initiatedBridge.BridgeTransfer,
}
}
if len(l1BridgeDeposits) > 0 {
log.Info("detected legacy L1StandardBridge deposits", "size", len(l1BridgeDeposits))
if err := db.BridgeTransfers.StoreL1BridgeDeposits(l1BridgeDeposits); err != nil {
return err
}
}
// a-ok!
return nil
}
func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, fromHeight *big.Int, toHeight *big.Int) error {
// (1) L2CrossDomainMessenger -> This is the root-most contract for which bridge events since withdrawals must be initiated from the L2CrossDomainMessenger
crossDomainSentMessages, err := contracts.CrossDomainMessengerSentMessageEvents("l2", predeploys.L2CrossDomainMessengerAddr, db, fromHeight, toHeight)
if err != nil {
return err
}
sentMessages := make(map[logKey]*contracts.CrossDomainMessengerSentMessageEvent, len(crossDomainSentMessages))
l2BridgeMessages := make([]database.L2BridgeMessage, len(crossDomainSentMessages))
l2TransactionWithdrawals := make([]database.L2TransactionWithdrawal, len(crossDomainSentMessages))
for i := range crossDomainSentMessages {
sentMessage := crossDomainSentMessages[i]
sentMessages[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex}] = &sentMessage
// To ensure consitency in our schema, we duplicate this as the "root" transaction withdrawal. We re-use the same withdrawal hash as the storage
// key for the proof sha3(calldata + sender) can be derived from the fields. (NOTE: should we just use the storage key here?)
l2BridgeMessages[i] = database.L2BridgeMessage{TransactionWithdrawalHash: sentMessage.BridgeMessage.MessageHash, BridgeMessage: sentMessage.BridgeMessage}
l2TransactionWithdrawals[i] = database.L2TransactionWithdrawal{
WithdrawalHash: sentMessage.BridgeMessage.MessageHash,
InitiatedL2EventGUID: sentMessage.Event.GUID,
Nonce: sentMessage.BridgeMessage.Nonce,
GasLimit: sentMessage.BridgeMessage.GasLimit,
Tx: database.Transaction{
FromAddress: sentMessage.BridgeMessage.Tx.FromAddress,
ToAddress: sentMessage.BridgeMessage.Tx.ToAddress,
Amount: big.NewInt(0),
Data: sentMessage.BridgeMessage.Tx.Data,
Timestamp: sentMessage.Event.Timestamp,
},
}
}
if len(l2BridgeMessages) > 0 {
log.Info("detected legacy transaction withdrawals (via L2CrossDomainMessenger)", "size", len(l2BridgeMessages))
if err := db.BridgeTransactions.StoreL2TransactionWithdrawals(l2TransactionWithdrawals); err != nil {
return err
}
if err := db.BridgeMessages.StoreL2BridgeMessages(l2BridgeMessages); err != nil {
return err
}
}
// (2) L2StandardBridge
initiatedBridges, err := contracts.L2StandardBridgeLegacyWithdrawalInitiatedEvents(predeploys.L2StandardBridgeAddr, db, fromHeight, toHeight)
if err != nil {
return err
}
l2BridgeWithdrawals := make([]database.L2BridgeWithdrawal, len(initiatedBridges))
for i := range initiatedBridges {
initiatedBridge := initiatedBridges[i]
// extract the cross domain message hash & deposit source hash from the following events
// Unlike bedrock, the bridge events are emitted AFTER sending the cross domain message
// - Event Flow: TransactionEnqueued -> SentMessage -> DepositInitiated
sentMessage, ok := sentMessages[logKey{initiatedBridge.Event.BlockHash, initiatedBridge.Event.LogIndex - 1}]
if !ok {
log.Error("missing cross domain message for bridge transfer", "tx_hash", initiatedBridge.Event.TransactionHash.String())
return fmt.Errorf("expected SentMessage preceding DepositInitiated event. tx_hash = %s", initiatedBridge.Event.TransactionHash)
}
initiatedBridge.BridgeTransfer.CrossDomainMessageHash = &sentMessage.BridgeMessage.MessageHash
l2BridgeWithdrawals[i] = database.L2BridgeWithdrawal{
TransactionWithdrawalHash: sentMessage.BridgeMessage.MessageHash,
BridgeTransfer: initiatedBridge.BridgeTransfer,
}
}
if len(l2BridgeWithdrawals) > 0 {
log.Info("detected legacy L2StandardBridge withdrawals", "size", len(l2BridgeWithdrawals))
if err := db.BridgeTransfers.StoreL2BridgeWithdrawals(l2BridgeWithdrawals); err != nil {
return err
}
}
// a-ok
return nil
}
// Legacy Bridge Finalization
func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, l1Client node.EthClient, chainConfig config.ChainConfig, fromHeight *big.Int, toHeight *big.Int) error {
// (1) L1CrossDomainMessenger -> This is the root-most contract from which bridge events are finalized since withdrawals must be initiated from the
// L2CrossDomainMessenger. Since there's no two-step withdrawal process, we mark the transaction as proven/finalized in the same step
crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l1", chainConfig.L1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
if err != nil {
return err
}
skippedPreRegenesisMessages := 0
for i := range crossDomainRelayedMessages {
relayedMessage := crossDomainRelayedMessages[i]
message, err := db.BridgeMessages.L2BridgeMessage(relayedMessage.MessageHash)
if err != nil {
return err
} else if message == nil {
// Before surfacing an error about a missing withdrawal, we need to handle an edge case
// for OP-Mainnet pre-regensis withdrawals that no longer exist on L2.
tx, err := l1Client.TxByHash(relayedMessage.Event.TransactionHash)
if err != nil {
return err
} else if tx == nil {
return errors.New("missing l1 tx hash for relayed message")
}
relayMessageData := tx.Data()[4:]
inputs, err := contracts.CrossDomainMessengerLegacyRelayMessageEncoding.Inputs.Unpack(relayMessageData)
if err != nil || inputs == nil {
return fmt.Errorf("unable to extract XDomainCallData from relayMessage transaction: %w", err)
}
// NOTE: Since OP-Mainnet is the only network to go through a regensis we can simply harcode the
// the starting message nonce at genesis (100k). Any relayed withdrawal on L1 with a lesser nonce
// is a clear indicator of a pre-regenesis withdrawal.
if inputs[3].(*big.Int).Int64() < 100_000 {
// skip pre-regenesis withdrawals
skippedPreRegenesisMessages++
continue
} else {
log.Error("missing indexed legacy L2CrossDomainMessenger message", "message_hash", relayedMessage.MessageHash, "tx_hash", relayedMessage.Event.TransactionHash)
return fmt.Errorf("missing indexed L2CrossDomainMessager message")
}
}
// Mark the associated tx withdrawal as proven/finalized with the same event
if err := db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
return err
}
if err := db.BridgeTransactions.MarkL2TransactionWithdrawalFinalizedEvent(relayedMessage.MessageHash, relayedMessage.Event.GUID, true); err != nil {
return err
}
if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
return err
}
}
if len(crossDomainRelayedMessages) > 0 {
relayedMessages := len(crossDomainRelayedMessages) - skippedPreRegenesisMessages
if skippedPreRegenesisMessages > 0 {
// Logged as a warning just for visibility
log.Warn("skipped pre-regensis relayed L2CrossDomainMessenger withdrawals", "size", skippedPreRegenesisMessages)
}
if relayedMessages > 0 {
log.Info("relayed legacy L2CrossDomainMessenger messages", "size", len(crossDomainRelayedMessages))
}
}
// (2) L1StandardBridge
// no-op for now as there's nothing actionable to do here besides sanity checks that we'll skip for now
// a-ok!
return nil
}
func LegacyL2ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, fromHeight *big.Int, toHeight *big.Int) error {
// (1) L2CrossDomainMessenger
crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l2", predeploys.L2CrossDomainMessengerAddr, db, fromHeight, toHeight)
if err != nil {
return err
}
for i := range crossDomainRelayedMessages {
relayedMessage := crossDomainRelayedMessages[i]
message, err := db.BridgeMessages.L1BridgeMessage(relayedMessage.MessageHash)
if err != nil {
return err
} else if message == nil {
log.Error("missing indexed legacy L1CrossDomainMessenger message", "message_hash", relayedMessage.MessageHash, "tx_hash", relayedMessage.Event.TransactionHash)
return fmt.Errorf("missing indexed L1CrossDomainMessager message")
}
if err := db.BridgeMessages.MarkRelayedL1BridgeMessage(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
return err
}
}
if len(crossDomainRelayedMessages) > 0 {
log.Info("relayed legacy L1CrossDomainMessenger messages", "size", len(crossDomainRelayedMessages))
}
// (2) L2StandardBridge
// no-op for now as there's nothing actionable to do here besides sanity checks that we'll skip for now
// a-ok!
return nil
}
......@@ -18,7 +18,7 @@ var (
bytesType, _ = abi.NewType("bytes", "", nil)
addressType, _ = abi.NewType("address", "", nil)
legacyCrossDomainMessengerRelayMessageMethod = abi.NewMethod(
CrossDomainMessengerLegacyRelayMessageEncoding = abi.NewMethod(
"relayMessage",
"relayMessage",
abi.Function,
......@@ -26,10 +26,13 @@ var (
false, // isConst
true, // payable
abi.Arguments{ // inputs
{Name: "sender", Type: addressType},
{Name: "target", Type: addressType},
{Name: "sender", Type: addressType},
{Name: "data", Type: bytesType},
{Name: "nonce", Type: uint256Type},
// The actual transaction on the legacy L1CrossDomainMessenger
// actually has a trailing proof argument but is ignored for the
// `XDomainCallData` encoding
},
abi.Arguments{}, // outputs
)
......@@ -58,7 +61,6 @@ func CrossDomainMessengerSentMessageEvents(chainSelector string, contractAddress
return nil, err
}
if len(sentMessageEvents) == 0 {
// prevent the following db queries if we dont need them
return nil, nil
}
......@@ -68,10 +70,13 @@ func CrossDomainMessengerSentMessageEvents(chainSelector string, contractAddress
if err != nil {
return nil, err
}
if len(sentMessageEvents) != len(sentMessageExtensionEvents) {
return nil, fmt.Errorf("mismatch in SentMessage events. %d sent messages & %d sent message extensions", len(sentMessageEvents), len(sentMessageExtensionEvents))
if len(sentMessageExtensionEvents) > len(sentMessageEvents) {
return nil, fmt.Errorf("mismatch. %d sent messages & %d sent message extensions", len(sentMessageEvents), len(sentMessageExtensionEvents))
}
// We handle version zero messages uniquely since the first version of cross domain messages
// do not have the SentMessageExtension1 event emitted, introduced in version 1.
numVersionZeroMessages := len(sentMessageEvents) - len(sentMessageExtensionEvents)
crossDomainSentMessages := make([]CrossDomainMessengerSentMessageEvent, len(sentMessageEvents))
for i := range sentMessageEvents {
sentMessage := bindings.CrossDomainMessengerSentMessage{Raw: *sentMessageEvents[i].RLPLog}
......@@ -79,13 +84,24 @@ func CrossDomainMessengerSentMessageEvents(chainSelector string, contractAddress
if err != nil {
return nil, err
}
version, _ := DecodeVersionedNonce(sentMessage.MessageNonce)
if i < numVersionZeroMessages && version != 0 {
return nil, fmt.Errorf("expected version zero nonce. nonce %d tx_hash %s", sentMessage.MessageNonce, sentMessage.Raw.TxHash)
}
// In version zero, to value is bridged through the cross domain messenger.
value := big.NewInt(0)
if version > 0 {
sentMessageExtension := bindings.CrossDomainMessengerSentMessageExtension1{Raw: *sentMessageExtensionEvents[i].RLPLog}
err = UnpackLog(&sentMessageExtension, sentMessageExtensionEvents[i].RLPLog, sentMessageExtensionEventAbi.Name, crossDomainMessengerAbi)
if err != nil {
return nil, err
}
value = sentMessageExtension.Value
}
messageHash, err := CrossDomainMessageHash(crossDomainMessengerAbi, &sentMessage, sentMessageExtension.Value)
messageHash, err := CrossDomainMessageHash(crossDomainMessengerAbi, &sentMessage, value)
if err != nil {
return nil, err
}
......@@ -100,13 +116,12 @@ func CrossDomainMessengerSentMessageEvents(chainSelector string, contractAddress
Tx: database.Transaction{
FromAddress: sentMessage.Sender,
ToAddress: sentMessage.Target,
Amount: sentMessageExtension.Value,
Amount: value,
Data: sentMessage.Message,
Timestamp: sentMessageEvents[i].Timestamp,
},
},
}
}
return crossDomainSentMessages, nil
......@@ -148,11 +163,11 @@ func CrossDomainMessageHash(abi *abi.ABI, sentMsg *bindings.CrossDomainMessenger
switch version {
case 0:
// Legacy Message
inputBytes, err := legacyCrossDomainMessengerRelayMessageMethod.Inputs.Pack(sentMsg.Sender, sentMsg.Target, sentMsg.Message, sentMsg.MessageNonce)
inputBytes, err := CrossDomainMessengerLegacyRelayMessageEncoding.Inputs.Pack(sentMsg.Target, sentMsg.Sender, sentMsg.Message, sentMsg.MessageNonce)
if err != nil {
return common.Hash{}, err
}
msgBytes := append(legacyCrossDomainMessengerRelayMessageMethod.ID, inputBytes...)
msgBytes := append(CrossDomainMessengerLegacyRelayMessageEncoding.ID, inputBytes...)
return crypto.Keccak256Hash(msgBytes), nil
case 1:
// Current Message
......
package contracts
import (
"math/big"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/common"
)
type LegacyBridgeEvent struct {
Event *database.ContractEvent
BridgeTransfer database.BridgeTransfer
}
func L1StandardBridgeLegacyDepositInitiatedEvents(contractAddress common.Address, db *database.DB, fromHeight, toHeight *big.Int) ([]LegacyBridgeEvent, error) {
l1StandardBridgeAbi, err := bindings.L1StandardBridgeMetaData.GetAbi()
if err != nil {
return nil, err
}
// The L1StandardBridge contains the legacy events
ethDepositEventAbi := l1StandardBridgeAbi.Events["ETHDepositInitiated"]
erc20DepositEventAbi := l1StandardBridgeAbi.Events["ERC20DepositInitiated"]
// Grab both ETH & ERC20 Events
ethDepositEvents, err := db.ContractEvents.L1ContractEventsWithFilter(database.ContractEvent{ContractAddress: contractAddress, EventSignature: ethDepositEventAbi.ID}, fromHeight, toHeight)
if err != nil {
return nil, err
}
erc20DepositEvents, err := db.ContractEvents.L1ContractEventsWithFilter(database.ContractEvent{ContractAddress: contractAddress, EventSignature: erc20DepositEventAbi.ID}, fromHeight, toHeight)
if err != nil {
return nil, err
}
// Represent the ETH deposits via the ETH ERC20 predeploy address
deposits := make([]LegacyBridgeEvent, len(ethDepositEvents)+len(erc20DepositEvents))
for i := range ethDepositEvents {
bridgeEvent := bindings.L1StandardBridgeETHDepositInitiated{Raw: *ethDepositEvents[i].RLPLog}
err := UnpackLog(&bridgeEvent, &bridgeEvent.Raw, ethDepositEventAbi.Name, l1StandardBridgeAbi)
if err != nil {
return nil, err
}
deposits[i] = LegacyBridgeEvent{
Event: &ethDepositEvents[i].ContractEvent,
BridgeTransfer: database.BridgeTransfer{
TokenPair: database.ETHTokenPair,
Tx: database.Transaction{
FromAddress: bridgeEvent.From,
ToAddress: bridgeEvent.To,
Amount: bridgeEvent.Amount,
Data: bridgeEvent.ExtraData,
Timestamp: ethDepositEvents[i].Timestamp,
},
},
}
}
for i := range erc20DepositEvents {
bridgeEvent := bindings.L1StandardBridgeERC20DepositInitiated{Raw: *erc20DepositEvents[i].RLPLog}
err := UnpackLog(&bridgeEvent, &bridgeEvent.Raw, erc20DepositEventAbi.Name, l1StandardBridgeAbi)
if err != nil {
return nil, err
}
deposits[len(ethDepositEvents)+i] = LegacyBridgeEvent{
Event: &erc20DepositEvents[i].ContractEvent,
BridgeTransfer: database.BridgeTransfer{
TokenPair: database.TokenPair{LocalTokenAddress: bridgeEvent.L1Token, RemoteTokenAddress: bridgeEvent.L2Token},
Tx: database.Transaction{
FromAddress: bridgeEvent.From,
ToAddress: bridgeEvent.To,
Amount: bridgeEvent.Amount,
Data: bridgeEvent.ExtraData,
Timestamp: erc20DepositEvents[i].Timestamp,
},
},
}
}
return deposits, nil
}
func L2StandardBridgeLegacyWithdrawalInitiatedEvents(contractAddress common.Address, db *database.DB, fromHeight, toHeight *big.Int) ([]LegacyBridgeEvent, error) {
l2StandardBridgeAbi, err := bindings.L2StandardBridgeMetaData.GetAbi()
if err != nil {
return nil, err
}
withdrawalInitiatedEventAbi := l2StandardBridgeAbi.Events["WithdrawalInitiated"]
withdrawalEvents, err := db.ContractEvents.L2ContractEventsWithFilter(database.ContractEvent{ContractAddress: contractAddress, EventSignature: withdrawalInitiatedEventAbi.ID}, fromHeight, toHeight)
withdrawals := make([]LegacyBridgeEvent, len(withdrawalEvents))
for i := range withdrawalEvents {
bridgeEvent := bindings.L2StandardBridgeWithdrawalInitiated{Raw: *withdrawalEvents[i].RLPLog}
err := UnpackLog(&bridgeEvent, &bridgeEvent.Raw, withdrawalInitiatedEventAbi.Name, l2StandardBridgeAbi)
if err != nil {
return nil, err
}
withdrawals[i] = LegacyBridgeEvent{
Event: &withdrawalEvents[i].ContractEvent,
BridgeTransfer: database.BridgeTransfer{
TokenPair: database.ETHTokenPair,
Tx: database.Transaction{
FromAddress: bridgeEvent.From,
ToAddress: bridgeEvent.To,
Amount: bridgeEvent.Amount,
Data: bridgeEvent.ExtraData,
Timestamp: withdrawalEvents[i].Timestamp,
},
},
}
}
return withdrawals, nil
}
package contracts
import (
"math/big"
"github.com/ethereum-optimism/optimism/indexer/database"
legacy_bindings "github.com/ethereum-optimism/optimism/op-bindings/legacy-bindings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
type LegacyCTCDepositEvent struct {
Event *database.ContractEvent
Tx database.Transaction
TxHash common.Hash
GasLimit *big.Int
}
func LegacyCTCDepositEvents(contractAddress common.Address, db *database.DB, fromHeight, toHeight *big.Int) ([]LegacyCTCDepositEvent, error) {
ctcAbi, err := legacy_bindings.CanonicalTransactionChainMetaData.GetAbi()
if err != nil {
return nil, err
}
transactionEnqueuedEventAbi := ctcAbi.Events["TransactionEnqueued"]
contractEventFilter := database.ContractEvent{ContractAddress: contractAddress, EventSignature: transactionEnqueuedEventAbi.ID}
events, err := db.ContractEvents.L1ContractEventsWithFilter(contractEventFilter, fromHeight, toHeight)
if err != nil {
return nil, err
}
ctcTxDeposits := make([]LegacyCTCDepositEvent, len(events))
for i := range events {
txEnqueued := legacy_bindings.CanonicalTransactionChainTransactionEnqueued{Raw: *events[i].RLPLog}
err = UnpackLog(&txEnqueued, events[i].RLPLog, transactionEnqueuedEventAbi.Name, ctcAbi)
if err != nil {
return nil, err
}
zeroAmt := big.NewInt(0)
ctcTxDeposits[i] = LegacyCTCDepositEvent{
Event: &events[i].ContractEvent,
GasLimit: txEnqueued.GasLimit,
TxHash: types.NewTransaction(0, txEnqueued.Target, zeroAmt, txEnqueued.GasLimit.Uint64(), nil, txEnqueued.Data).Hash(),
Tx: database.Transaction{
FromAddress: txEnqueued.L1TxOrigin,
ToAddress: txEnqueued.Target,
Amount: zeroAmt,
Data: txEnqueued.Data,
Timestamp: events[i].Timestamp,
},
}
}
return ctcTxDeposits, nil
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment