Commit d93a9a1a authored by Hamdi Allam's avatar Hamdi Allam

standard_bridge.go

parent 02f826b6
...@@ -52,8 +52,9 @@ type LegacyStateBatch struct { ...@@ -52,8 +52,9 @@ type LegacyStateBatch struct {
} }
type OutputProposal struct { type OutputProposal struct {
OutputRoot common.Hash `gorm:"primaryKey;serializer:json"` OutputRoot common.Hash `gorm:"primaryKey;serializer:json"`
L2BlockNumber U256 L2BlockNumber U256
L1ContractEventGUID uuid.UUID L1ContractEventGUID uuid.UUID
} }
......
...@@ -18,7 +18,7 @@ CREATE TABLE IF NOT EXISTS l2_block_headers ( ...@@ -18,7 +18,7 @@ CREATE TABLE IF NOT EXISTS l2_block_headers (
hash VARCHAR NOT NULL PRIMARY KEY, hash VARCHAR NOT NULL PRIMARY KEY,
parent_hash VARCHAR NOT NULL, parent_hash VARCHAR NOT NULL,
number UINT256, number UINT256,
timestamp INTEGER NOT NULL CHECK (timestamp > 0), timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
/** /**
...@@ -70,7 +70,7 @@ CREATE TABLE IF NOT EXISTS deposits ( ...@@ -70,7 +70,7 @@ CREATE TABLE IF NOT EXISTS deposits (
-- Event causing the deposit -- Event causing the deposit
initiated_l1_event_guid VARCHAR NOT NULL REFERENCES l1_contract_events(guid), initiated_l1_event_guid VARCHAR NOT NULL REFERENCES l1_contract_events(guid),
sent_message_nonce UINT256 NOT NULL UNIQUE, sent_message_nonce UINT256 UNIQUE,
-- Finalization marker for the deposit -- Finalization marker for the deposit
finalized_l2_event_guid VARCHAR REFERENCES l2_contract_events(guid), finalized_l2_event_guid VARCHAR REFERENCES l2_contract_events(guid),
...@@ -91,7 +91,7 @@ CREATE TABLE IF NOT EXISTS withdrawals ( ...@@ -91,7 +91,7 @@ CREATE TABLE IF NOT EXISTS withdrawals (
-- Event causing this withdrawal -- Event causing this withdrawal
initiated_l2_event_guid VARCHAR NOT NULL REFERENCES l2_contract_events(guid), initiated_l2_event_guid VARCHAR NOT NULL REFERENCES l2_contract_events(guid),
sent_message_nonce UINT256 NOT NULL UNIQUE, sent_message_nonce UINT256 UNIQUE,
-- Multistep (bedrock) process of a withdrawal -- Multistep (bedrock) process of a withdrawal
withdrawal_hash VARCHAR NOT NULL, withdrawal_hash VARCHAR NOT NULL,
......
package processor
import (
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/google/uuid"
)
type ProcessedContractEvents struct {
events []*database.ContractEvent
eventsBySignature map[common.Hash][]*database.ContractEvent
eventByLogIndex map[uint]*database.ContractEvent
eventLog map[uuid.UUID]*types.Log
}
func NewProcessedContractEvents() *ProcessedContractEvents {
return &ProcessedContractEvents{
events: []*database.ContractEvent{},
eventsBySignature: make(map[common.Hash][]*database.ContractEvent),
eventByLogIndex: make(map[uint]*database.ContractEvent),
eventLog: make(map[uuid.UUID]*types.Log),
}
}
func (p *ProcessedContractEvents) AddLog(log *types.Log, time uint64) *database.ContractEvent {
contractEvent := database.ContractEventFromLog(log, time)
p.events = append(p.events, &contractEvent)
p.eventsBySignature[contractEvent.EventSignature] = append(p.eventsBySignature[contractEvent.EventSignature], &contractEvent)
p.eventByLogIndex[log.Index] = &contractEvent
p.eventLog[contractEvent.GUID] = log
return &contractEvent
}
func DecodeFromProcessedEvents[ABI any](p *ProcessedContractEvents, name string, contractAbi *abi.ABI) ([]*ABI, error) {
eventAbi, ok := contractAbi.Events[name]
if !ok {
return nil, errors.New(fmt.Sprintf("event %s not present in supplied ABI", name))
}
decodedEvents := []*ABI{}
for _, event := range p.eventsBySignature[eventAbi.ID] {
log := p.eventLog[event.GUID]
var decodedEvent ABI
err := contractAbi.UnpackIntoInterface(&decodedEvent, name, log.Data)
if err != nil {
return nil, err
}
// handle topics if present
if len(log.Topics) > 1 {
var indexedArgs abi.Arguments
for _, arg := range eventAbi.Inputs {
if arg.Indexed {
indexedArgs = append(indexedArgs, arg)
}
}
// The first topic (event signature) is ommitted
err := abi.ParseTopics(&decodedEvent, indexedArgs, log.Topics[1:])
if err != nil {
return nil, err
}
}
decodedEvents = append(decodedEvents, &decodedEvent)
}
return decodedEvents, nil
}
func UnpackLog[EventType any](log *types.Log, name string, contractAbi *abi.ABI) (*EventType, error) {
eventAbi, ok := contractAbi.Events[name]
if !ok {
return nil, errors.New(fmt.Sprintf("event %s not present in supplied ABI", name))
} else if len(log.Topics) == 0 {
return nil, errors.New("anonymous events are not supported")
} else if log.Topics[0] != eventAbi.ID {
return nil, errors.New("event signature mismatch not present in supplied ABI")
}
var event EventType
err := contractAbi.UnpackIntoInterface(&event, name, log.Data)
if err != nil {
return nil, err
}
// handle topics if present
if len(log.Topics) > 1 {
var indexedArgs abi.Arguments
for _, arg := range eventAbi.Inputs {
if arg.Indexed {
indexedArgs = append(indexedArgs, arg)
}
}
// The first topic (event signature) is ommitted
err := abi.ParseTopics(&event, indexedArgs, log.Topics[1:])
if err != nil {
return nil, err
}
}
return &event, nil
}
package processor package processor
import ( import (
"bytes"
"context" "context"
"encoding/hex" "encoding/hex"
"errors" "errors"
...@@ -23,10 +22,6 @@ import ( ...@@ -23,10 +22,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
var (
ethAddress = common.HexToAddress("0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000")
)
type L1Contracts struct { type L1Contracts struct {
OptimismPortal common.Address OptimismPortal common.Address
L2OutputOracle common.Address L2OutputOracle common.Address
...@@ -135,16 +130,14 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1 ...@@ -135,16 +130,14 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1
return err return err
} }
// L2 checkpoitns posted on L1 // L2 checkpoints posted on L1
outputProposals := []*database.OutputProposal{} outputProposals := []*database.OutputProposal{}
legacyStateBatches := []*database.LegacyStateBatch{} legacyStateBatches := []*database.LegacyStateBatch{}
numLogs := len(logs)
logsByIndex := make(map[uint]*types.Log, numLogs)
l1ContractEvents := make([]*database.L1ContractEvent, numLogs)
l1ContractEventLogs := make(map[uuid.UUID]*types.Log)
l1HeadersOfInterest := make(map[common.Hash]bool) l1HeadersOfInterest := make(map[common.Hash]bool)
l1ContractEvents := make([]*database.L1ContractEvent, len(logs))
processedContractEvents := NewProcessedContractEvents()
for i, log := range logs { for i, log := range logs {
header, ok := headerMap[log.BlockHash] header, ok := headerMap[log.BlockHash]
if !ok { if !ok {
...@@ -152,12 +145,9 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1 ...@@ -152,12 +145,9 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1
return errors.New("parsed log with a block hash not in this batch") return errors.New("parsed log with a block hash not in this batch")
} }
logsByIndex[log.Index] = &logs[i] contractEvent := processedContractEvents.AddLog(&logs[i], header.Time)
contractEvent := &database.L1ContractEvent{ContractEvent: database.ContractEventFromLog(&log, header.Time)}
l1ContractEvents[i] = contractEvent
l1ContractEventLogs[contractEvent.GUID] = &logs[i]
l1HeadersOfInterest[log.BlockHash] = true l1HeadersOfInterest[log.BlockHash] = true
l1ContractEvents[i] = &database.L1ContractEvent{ContractEvent: *contractEvent}
// Track Checkpoint Events for L2 // Track Checkpoint Events for L2
switch contractEvent.EventSignature { switch contractEvent.EventSignature {
...@@ -202,24 +192,21 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1 ...@@ -202,24 +192,21 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1
continue continue
} }
indexedL1Headers = append( indexedL1Headers = append(indexedL1Headers, &database.L1BlockHeader{BlockHeader: database.BlockHeaderFromHeader(header)})
indexedL1Headers,
&database.L1BlockHeader{BlockHeader: database.BlockHeaderFromHeader(header)},
)
} }
/** Update Database **/ /** Update Database **/
numIndexedL1Headers := len(indexedL1Headers) numIndexedL1Headers := len(indexedL1Headers)
if numIndexedL1Headers > 0 { if numIndexedL1Headers > 0 {
processLog.Info("saved l1 blocks of interest within batch", "num", numIndexedL1Headers, "batchSize", numHeaders) processLog.Info("saving l1 blocks with optimism logs", "size", numIndexedL1Headers, "batch_size", numHeaders)
err = db.Blocks.StoreL1BlockHeaders(indexedL1Headers) err = db.Blocks.StoreL1BlockHeaders(indexedL1Headers)
if err != nil { if err != nil {
return err return err
} }
// Since the headers to index are derived from the existence of logs, we know in this branch `numLogs > 0` // Since the headers to index are derived from the existence of logs, we know in this branch `numLogs > 0`
processLog.Info("saving contract logs", "size", numLogs) processLog.Info("detected contract logs", "size", len(l1ContractEvents))
err = db.ContractEvents.StoreL1ContractEvents(l1ContractEvents) err = db.ContractEvents.StoreL1ContractEvents(l1ContractEvents)
if err != nil { if err != nil {
return err return err
...@@ -244,7 +231,7 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1 ...@@ -244,7 +231,7 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1
} }
// forward along contract events to the bridge processor // forward along contract events to the bridge processor
err = l1BridgeProcessContractEvents(processLog, db, l1ContractEvents, l1ContractEventLogs, logsByIndex) err = l1BridgeProcessContractEvents(processLog, db, processedContractEvents)
if err != nil { if err != nil {
return err return err
} }
...@@ -257,86 +244,35 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1 ...@@ -257,86 +244,35 @@ func l1ProcessFn(processLog log.Logger, ethClient node.EthClient, l1Contracts L1
} }
} }
func l1BridgeProcessContractEvents( func l1BridgeProcessContractEvents(processLog log.Logger, db *database.DB, events *ProcessedContractEvents) error {
processLog log.Logger, initiatedBridgeEvents, err := StandardBridgeInitiatedEvents(events)
db *database.DB,
events []*database.L1ContractEvent,
eventLogs map[uuid.UUID]*types.Log,
logsByIndex map[uint]*types.Log,
) error {
l1StandardBridgeABI, err := bindings.L1StandardBridgeMetaData.GetAbi()
if err != nil { if err != nil {
return err return err
} }
l1CrossDomainMessengerABI, err := bindings.L1CrossDomainMessengerMetaData.GetAbi() l1StandardBridgeDeposits := make([]*database.Deposit, len(initiatedBridgeEvents))
if err != nil { for i, initiatedBridgeEvent := range initiatedBridgeEvents {
return err l1StandardBridgeDeposits[i] = &database.Deposit{
} GUID: uuid.New(),
InitiatedL1EventGUID: initiatedBridgeEvent.RawEvent.GUID,
l1StandardBridgeDeposits := []*database.Deposit{} SentMessageNonce: database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
ethBridgeInitiatedEventSig := l1StandardBridgeABI.Events["ETHBridgeInitiated"].ID TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
sentMessageEventSig := l1CrossDomainMessengerABI.Events["SentMessage"].ID Tx: database.Transaction{
for _, contractEvent := range events { FromAddress: initiatedBridgeEvent.From,
eventSig := contractEvent.EventSignature ToAddress: initiatedBridgeEvent.To,
log := eventLogs[contractEvent.GUID] Amount: database.U256{Int: initiatedBridgeEvent.Amount},
if eventSig == ethBridgeInitiatedEventSig { Data: initiatedBridgeEvent.ExtraData,
// (1) Deconstruct the bridge event Timestamp: initiatedBridgeEvent.RawEvent.Timestamp,
var bridgeData bindings.L1StandardBridgeETHBridgeInitiated },
err = l1StandardBridgeABI.UnpackIntoInterface(&bridgeData, "ETHBridgeInitiated", log.Data)
if err != nil || len(log.Topics) != 3 {
processLog.Crit("unexpected ETHDepositInitiated log format", "tx", log.TxHash, "err", err)
return err
}
// from/to must be retrieved from log topics
bridgeData.From = common.BytesToAddress(log.Topics[1].Bytes())
bridgeData.To = common.BytesToAddress(log.Topics[2].Bytes())
// (2) Look for the sent message event to extract the associated messager nonce
// - The `SentMessage` event is the second after the bridge initiated event. BridgeInitiated -> Portal#DepositTransaction -> SentMesage ...
sentMsgLog := logsByIndex[log.Index+2]
if sentMsgLog.Topics[0] != sentMessageEventSig {
processLog.Crit("expected CrossDomainMessenger#SentMessage to follow StandardBridge#EthBridgeInitiated event", "event_sig", sentMsgLog.Topics[0], "sent_message_sig", sentMessageEventSig)
return errors.New("unexpected bridge event ordering")
}
expectedMsg, err := l1StandardBridgeABI.Pack("finalizeBridgeETH", bridgeData.From, bridgeData.To, bridgeData.Amount, bridgeData.ExtraData)
if err != nil {
processLog.Crit("unable to create bridge message")
return err
}
var sentMsg bindings.L1CrossDomainMessengerSentMessage
err = l1CrossDomainMessengerABI.UnpackIntoInterface(&sentMsg, "SentMessage", sentMsgLog.Data)
if err != nil {
processLog.Crit("unexpected SentMessage log format", "tx", log.TxHash, "err", err)
return err
} else if !bytes.Equal(sentMsg.Message, expectedMsg) {
processLog.Crit("SentMessage message mismatch", "expected_bridge_msg", hex.EncodeToString(expectedMsg), "event_msg", hex.EncodeToString(sentMsg.Message))
return errors.New("bridge message mismatch")
}
// (3) Record the deposit
l1StandardBridgeDeposits = append(l1StandardBridgeDeposits, &database.Deposit{
GUID: uuid.New(),
InitiatedL1EventGUID: contractEvent.GUID,
SentMessageNonce: database.U256{Int: sentMsg.MessageNonce},
TokenPair: database.TokenPair{L1TokenAddress: ethAddress, L2TokenAddress: ethAddress},
Tx: database.Transaction{
FromAddress: common.BytesToAddress(log.Topics[1].Bytes()),
ToAddress: common.BytesToAddress(log.Topics[2].Bytes()),
Amount: database.U256{Int: bridgeData.Amount},
Data: bridgeData.ExtraData,
Timestamp: contractEvent.Timestamp,
},
})
} }
} }
if len(l1StandardBridgeDeposits) > 0 { if len(l1StandardBridgeDeposits) > 0 {
processLog.Info("detected L1StandardBridge deposits", "num", len(l1StandardBridgeDeposits)) processLog.Info("detected L1StandardBridge deposits", "num", len(l1StandardBridgeDeposits))
return db.Bridge.StoreDeposits(l1StandardBridgeDeposits) err := db.Bridge.StoreDeposits(l1StandardBridgeDeposits)
if err != nil {
return err
}
} }
// no-op // no-op
......
package processor package processor
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"math/big"
"reflect" "reflect"
"github.com/ethereum-optimism/optimism/indexer/database" "github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node" "github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/google/uuid"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -128,11 +124,8 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -128,11 +124,8 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2
return err return err
} }
numLogs := len(logs) l2ContractEvents := make([]*database.L2ContractEvent, len(logs))
logsByIndex := make(map[uint]*types.Log, numLogs) processedContractEvents := NewProcessedContractEvents()
l2ContractEvents := make([]*database.L2ContractEvent, numLogs)
l2ContractEventLogs := make(map[uuid.UUID]*types.Log)
for i, log := range logs { for i, log := range logs {
header, ok := l2HeaderMap[log.BlockHash] header, ok := l2HeaderMap[log.BlockHash]
if !ok { if !ok {
...@@ -140,11 +133,8 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -140,11 +133,8 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2
return errors.New("parsed log with a block hash not in this batch") return errors.New("parsed log with a block hash not in this batch")
} }
logsByIndex[log.Index] = &logs[i] contractEvent := processedContractEvents.AddLog(&logs[i], header.Time)
contractEvent := &database.L2ContractEvent{ContractEvent: database.ContractEventFromLog(&log, header.Time)} l2ContractEvents[i] = &database.L2ContractEvent{ContractEvent: *contractEvent}
l2ContractEvents[i] = contractEvent
l2ContractEventLogs[contractEvent.GUID] = &logs[i]
} }
/** Update Database **/ /** Update Database **/
...@@ -155,6 +145,7 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -155,6 +145,7 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2
return err return err
} }
numLogs := len(l2ContractEvents)
if numLogs > 0 { if numLogs > 0 {
processLog.Info("detected contract logs", "size", numLogs) processLog.Info("detected contract logs", "size", numLogs)
err = db.ContractEvents.StoreL2ContractEvents(l2ContractEvents) err = db.ContractEvents.StoreL2ContractEvents(l2ContractEvents)
...@@ -163,7 +154,7 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -163,7 +154,7 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2
} }
// forward along contract events to the bridge processor // forward along contract events to the bridge processor
err = l2BridgeProcessContractEvents(processLog, db, ethClient, l2ContractEvents, l2ContractEventLogs, logsByIndex) err = l2BridgeProcessContractEvents(processLog, db, ethClient, processedContractEvents)
if err != nil { if err != nil {
return err return err
} }
...@@ -174,104 +165,51 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -174,104 +165,51 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2
} }
} }
func l2BridgeProcessContractEvents( func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethClient node.EthClient, events *ProcessedContractEvents) error {
processLog log.Logger,
db *database.DB,
ethClient node.EthClient,
events []*database.L2ContractEvent,
eventLogs map[uuid.UUID]*types.Log,
logsByIndex map[uint]*types.Log,
) error {
rawEthClient := ethclient.NewClient(ethClient.RawRpcClient()) rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())
l2StandardBridgeABI, err := bindings.L2StandardBridgeMetaData.GetAbi() finalizationBridgeEvents, err := StandardBridgeFinalizedEvents(rawEthClient, events)
if err != nil { if err != nil {
return err return err
} }
l2CrossDomainMessengerABI, err := bindings.L2CrossDomainMessengerMetaData.GetAbi() for _, finalizedBridgeEvent := range finalizationBridgeEvents {
if err != nil { nonce := finalizedBridgeEvent.CrossDomainMessengerNonce
return err
}
numFinalizedDeposits := 0
ethBridgeFinalizedEventSig := l2StandardBridgeABI.Events["ETHBridgeFinalized"].ID
relayedMessageEventSig := l2CrossDomainMessengerABI.Events["RelayedMessage"].ID
relayMessageMethod := l2CrossDomainMessengerABI.Methods["relayMessage"]
for _, contractEvent := range events {
eventSig := contractEvent.EventSignature
log := eventLogs[contractEvent.GUID]
if eventSig == ethBridgeFinalizedEventSig {
// (1) Ensure the RelayedMessage follows the log right after the bridge event
relayedMsgLog := logsByIndex[log.Index+1]
if relayedMsgLog.Topics[0] != relayedMessageEventSig {
processLog.Crit("expected CrossDomainMessenger#RelayedMessage following StandardBridge#EthBridgeFinalized event", "event_sig", relayedMsgLog.Topics[0], "relayed_message_sig", relayedMessageEventSig)
return errors.New("unexpected bridge event ordering")
}
// There's no way to extract the nonce on the relayed message event. we can extract deposit, err := db.Bridge.DepositByMessageNonce(nonce)
// the nonce by unpacking the transaction input for the `relayMessage` transaction if err != nil {
tx, isPending, err := rawEthClient.TransactionByHash(context.Background(), relayedMsgLog.TxHash) processLog.Error("error querying associated deposit messsage using nonce", "cross_domain_messenger_nonce", nonce)
if err != nil || isPending { return err
processLog.Crit("CrossDomainMessager#relayeMessage transaction query err or found pending", err, "err", "isPending", isPending) } else if deposit == nil {
return errors.New("unable to query relayMessage tx") latestNonce, err := db.Bridge.LatestDepositMessageNonce()
}
txData := tx.Data()
if !bytes.Equal(txData[:4], relayMessageMethod.ID) {
processLog.Crit("expected relayMessage function selector")
return errors.New("RelayMessage log does not match relayMessage transaction")
}
inputsMap := make(map[string]interface{})
err = relayMessageMethod.Inputs.UnpackIntoMap(inputsMap, txData[4:])
if err != nil {
processLog.Crit("unable to unpack CrossDomainMessenger#relayMessage function data", "err", err)
return err
}
nonce, ok := inputsMap["_nonce"].(*big.Int)
if !ok {
processLog.Crit("unable to extract _nonce from CrossDomainMessenger#relayMessage function call")
return errors.New("unable to extract relayMessage nonce")
}
// (2) Mark initiated L1 deposit as finalized
deposit, err := db.Bridge.DepositByMessageNonce(nonce)
if err != nil { if err != nil {
processLog.Error("error querying initiated deposit messsage using nonce", "nonce", nonce)
return err return err
} else if deposit == nil {
latestNonce, err := db.Bridge.LatestDepositMessageNonce()
if err != nil {
return err
}
// Check if the L1Processor is behind or really has missed an event. Since the decimal representation
// of the nonce will be large (first two bytes indicate the version), we log the nonce as needed in hex format
if latestNonce == nil || nonce.Cmp(latestNonce) > 0 {
processLog.Warn("behind on indexed L1 deposits", "deposit_message_nonce", hexutil.EncodeBig(nonce), "latest_deposit_message_nonce", hexutil.EncodeBig(latestNonce))
return errors.New("waiting for L1Processor to catch up")
} else {
processLog.Crit("missing indexed deposit for this finalization event", "deposit_message_nonce", hexutil.EncodeBig(nonce), "tx_hash", log.TxHash, "log_index", log.Index)
return errors.New("missing deposit message")
}
} }
err = db.Bridge.MarkFinalizedDepositEvent(deposit.GUID, contractEvent.GUID) // Check if the L1Processor is behind or really has missed an event. Since the decimal representation
if err != nil { // of the nonce will be large (first two bytes indicate the version), we log the nonce as needed in hex format
processLog.Error("error finalizing deposit", "err", err) if latestNonce == nil || nonce.Cmp(latestNonce) > 0 {
return err processLog.Warn("behind on indexed L1 deposits", "deposit_message_nonce", hexutil.EncodeBig(nonce), "latest_deposit_message_nonce", hexutil.EncodeBig(latestNonce))
return errors.New("waiting for L1Processor to catch up")
} else {
log := events.eventLog[finalizedBridgeEvent.RawEvent.GUID]
processLog.Crit("missing indexed deposit for this finalization event", "deposit_cross_domain_message_nonce", hexutil.EncodeBig(nonce), "tx_hash", log.TxHash)
return errors.New("missing deposit message")
} }
}
numFinalizedDeposits++ err = db.Bridge.MarkFinalizedDepositEvent(deposit.GUID, finalizedBridgeEvent.RawEvent.GUID)
if err != nil {
processLog.Error("error finalizing deposit", "err", err)
return err
} }
} }
// a-ok! numFinalizedDeposits := len(finalizationBridgeEvents)
if numFinalizedDeposits > 0 { if numFinalizedDeposits > 0 {
processLog.Info("finalized deposits", "num", numFinalizedDeposits) processLog.Info("finalized deposits", "size", numFinalizedDeposits)
} }
// a-ok
return nil return nil
} }
package processor
import (
"bytes"
"context"
"errors"
"math/big"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
var (
ethAddress = common.HexToAddress("0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000")
)
type StandardBridgeInitiatedEvent struct {
*bindings.L1StandardBridgeERC20BridgeInitiated
CrossDomainMessengerNonce *big.Int
RawEvent *database.ContractEvent
}
type StandardBridgeFinalizedEvent struct {
*bindings.L1StandardBridgeERC20BridgeFinalized
CrossDomainMessengerNonce *big.Int
RawEvent *database.ContractEvent
}
// StandardBridgeInitiatedEvents extracts all initiated bridge events from the contracts that follow the StandardBridge ABI. The
// correlated CrossDomainMessenger nonce is also parsed from the associated messenger events.
func StandardBridgeInitiatedEvents(events *ProcessedContractEvents) ([]*StandardBridgeInitiatedEvent, error) {
l1StandardBridgeABI, err := bindings.L1StandardBridgeMetaData.GetAbi()
if err != nil {
return nil, err
}
l1CrossDomainMessengerABI, err := bindings.L1CrossDomainMessengerMetaData.GetAbi()
if err != nil {
return nil, err
}
ethBridgeInitiatedEventAbi := l1StandardBridgeABI.Events["ETHBridgeInitiated"]
erc20BridgeInitiatedEventAbi := l1StandardBridgeABI.Events["ERC20BridgeInitiated"]
sentMessageEventAbi := l1CrossDomainMessengerABI.Events["SentMessage"]
ethBridgeInitiatedEvents := events.eventsBySignature[ethBridgeInitiatedEventAbi.ID]
erc20BridgeInitiatedEvents := events.eventsBySignature[erc20BridgeInitiatedEventAbi.ID]
initiatedBridgeEvents := []*StandardBridgeInitiatedEvent{}
// Handle ETH Bridge
for _, bridgeInitiatedEvent := range ethBridgeInitiatedEvents {
log := events.eventLog[bridgeInitiatedEvent.GUID]
bridgeData, err := UnpackLog[bindings.L1StandardBridgeETHBridgeInitiated](log, ethBridgeInitiatedEventAbi.Name, l1StandardBridgeABI)
if err != nil {
return nil, err
}
// Look for the sent message event to extract the associated messager nonce
// - The `SentMessage` event is the second after the bridge initiated event. BridgeInitiated -> Portal#DepositTransaction -> SentMesage ...
sentMsgLog := events.eventLog[events.eventByLogIndex[log.Index+2].GUID]
sentMsgData, err := UnpackLog[bindings.L1CrossDomainMessengerSentMessage](sentMsgLog, sentMessageEventAbi.Name, l1CrossDomainMessengerABI)
if err != nil {
return nil, err
}
expectedMsg, err := l1StandardBridgeABI.Pack("finalizeBridgeETH", bridgeData.From, bridgeData.To, bridgeData.Amount, bridgeData.ExtraData)
if err != nil {
return nil, err
} else if !bytes.Equal(sentMsgData.Message, expectedMsg) {
return nil, errors.New("bridge cross domain message mismatch")
}
initiatedBridgeEvents = append(initiatedBridgeEvents, &StandardBridgeInitiatedEvent{
&bindings.L1StandardBridgeERC20BridgeInitiated{
// Default ETH
LocalToken: ethAddress, RemoteToken: ethAddress,
// BridgeDAta
From: bridgeData.From, To: bridgeData.To, Amount: bridgeData.Amount, ExtraData: bridgeData.ExtraData,
},
sentMsgData.MessageNonce,
bridgeInitiatedEvent,
})
}
// Handle ERC20 Bridge
for _, bridgeInitiatedEvent := range erc20BridgeInitiatedEvents {
log := events.eventLog[bridgeInitiatedEvent.GUID]
bridgeData, err := UnpackLog[bindings.L1StandardBridgeERC20BridgeInitiated](log, erc20BridgeInitiatedEventAbi.Name, l1StandardBridgeABI)
if err != nil {
return nil, err
}
// Look for the sent message event to extract the associated messager nonce
// - The `SentMessage` event is the second after the bridge initiated event. BridgeInitiated -> Portal#DepositTransaction -> SentMesage ...
sentMsgLog := events.eventLog[events.eventByLogIndex[log.Index+2].GUID]
sentMsgData, err := UnpackLog[bindings.L1CrossDomainMessengerSentMessage](sentMsgLog, sentMessageEventAbi.Name, l1CrossDomainMessengerABI)
if err != nil {
return nil, err
}
expectedMsg, err := l1StandardBridgeABI.Pack("finalizeBridgeERC20", bridgeData.RemoteToken, bridgeData.LocalToken, bridgeData.From, bridgeData.To, bridgeData.Amount, bridgeData.ExtraData)
if err != nil {
return nil, err
} else if !bytes.Equal(sentMsgData.Message, expectedMsg) {
return nil, errors.New("bridge cross domain message mismatch")
}
initiatedBridgeEvents = append(initiatedBridgeEvents, &StandardBridgeInitiatedEvent{bridgeData, sentMsgData.MessageNonce, bridgeInitiatedEvent})
}
return initiatedBridgeEvents, nil
}
// StandardBridgeFinalizedEvents extracts all finalization bridge events from the contracts that follow the StandardBridge ABI. The
// correlated CrossDomainMessenger nonce is also parsed by looking at the parameters of the corresponding relayMessage transaction data.
func StandardBridgeFinalizedEvents(rawEthClient *ethclient.Client, events *ProcessedContractEvents) ([]*StandardBridgeFinalizedEvent, error) {
l1StandardBridgeABI, err := bindings.L1StandardBridgeMetaData.GetAbi()
if err != nil {
return nil, err
}
l1CrossDomainMessengerABI, err := bindings.L1CrossDomainMessengerMetaData.GetAbi()
if err != nil {
return nil, err
}
ethBridgeFinalizedEventAbi := l1StandardBridgeABI.Events["ETHBridgeFinalized"]
erc20BridgeFinalizedEventAbi := l1StandardBridgeABI.Events["ERC20BridgeFinalized"]
relayedMessageEventAbi := l1CrossDomainMessengerABI.Events["RelayedMessage"]
relayMessageMethodAbi := l1CrossDomainMessengerABI.Methods["relayMessage"]
ethBridgeFinalizedEvents := events.eventsBySignature[ethBridgeFinalizedEventAbi.ID]
erc20BridgeFinalizedEvents := events.eventsBySignature[erc20BridgeFinalizedEventAbi.ID]
finalizedBridgeEvents := []*StandardBridgeFinalizedEvent{}
// Handle ETH Bridge
for _, bridgeFinalizedEvent := range ethBridgeFinalizedEvents {
log := events.eventLog[bridgeFinalizedEvent.GUID]
bridgeData, err := UnpackLog[bindings.L1StandardBridgeETHBridgeFinalized](log, ethBridgeFinalizedEventAbi.Name, l1StandardBridgeABI)
if err != nil {
return nil, err
}
// Look for the RelayedMessage event that follows right after the BridgeFinalized Event
relayedMsgLog := events.eventLog[events.eventByLogIndex[log.Index+1].GUID]
if relayedMsgLog.Topics[0] != relayedMessageEventAbi.ID {
return nil, errors.New("unexpected bridge event ordering")
}
// There's no way to extract the nonce on the relayed message event. we can extract
// the nonce by unpacking the transaction input for the `relayMessage` transaction
tx, isPending, err := rawEthClient.TransactionByHash(context.Background(), relayedMsgLog.TxHash)
if err != nil || isPending {
return nil, errors.New("unable to query relayMessage tx for bridge finalization event")
}
txData := tx.Data()
if !bytes.Equal(txData[:4], relayMessageMethodAbi.ID) {
return nil, errors.New("bridge finalization event does not match relayMessage tx invocation")
}
inputsMap := make(map[string]interface{})
err = relayMessageMethodAbi.Inputs.UnpackIntoMap(inputsMap, txData[4:])
if err != nil {
return nil, err
}
nonce, ok := inputsMap["_nonce"].(*big.Int)
if !ok {
return nil, errors.New("unable to extract `_nonce` parameter from relayMessage transaction")
}
finalizedBridgeEvents = append(finalizedBridgeEvents, &StandardBridgeFinalizedEvent{
&bindings.L1StandardBridgeERC20BridgeFinalized{
// Default ETH
LocalToken: ethAddress, RemoteToken: ethAddress,
// BridgeDAta
From: bridgeData.From, To: bridgeData.To, Amount: bridgeData.Amount, ExtraData: bridgeData.ExtraData,
},
nonce,
bridgeFinalizedEvent,
})
}
// Handle ERC20 Bridge
for _, bridgeFinalizedEvent := range erc20BridgeFinalizedEvents {
log := events.eventLog[bridgeFinalizedEvent.GUID]
bridgeData, err := UnpackLog[bindings.L1StandardBridgeERC20BridgeFinalized](log, erc20BridgeFinalizedEventAbi.Name, l1StandardBridgeABI)
if err != nil {
return nil, err
}
// Look for the RelayedMessage event that follows right after the BridgeFinalized Event
relayedMsgLog := events.eventLog[events.eventByLogIndex[log.Index+1].GUID]
if relayedMsgLog.Topics[0] != relayedMessageEventAbi.ID {
return nil, errors.New("unexpected bridge event ordering")
}
// There's no way to extract the nonce on the relayed message event. we can extract
// the nonce by unpacking the transaction input for the `relayMessage` transaction
tx, isPending, err := rawEthClient.TransactionByHash(context.Background(), relayedMsgLog.TxHash)
if err != nil || isPending {
return nil, errors.New("unable to query relayMessage tx for bridge finalization event")
}
txData := tx.Data()
if !bytes.Equal(txData[:4], relayMessageMethodAbi.ID) {
return nil, errors.New("bridge finalization event does not match relayMessage tx invocation")
}
inputsMap := make(map[string]interface{})
err = relayMessageMethodAbi.Inputs.UnpackIntoMap(inputsMap, txData[4:])
if err != nil {
return nil, err
}
nonce, ok := inputsMap["_nonce"].(*big.Int)
if !ok {
return nil, errors.New("unable to extract `_nonce` parameter from relayMessage transaction")
}
finalizedBridgeEvents = append(finalizedBridgeEvents, &StandardBridgeFinalizedEvent{bridgeData, nonce, bridgeFinalizedEvent})
}
return finalizedBridgeEvents, 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