Commit 60111482 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into inphi/update-ci-builder

parents af15ddc2 129c22c8
---
'@eth-optimism/sdk': patch
---
Fixed missing indexes for multicall support
...@@ -24,7 +24,7 @@ import ( ...@@ -24,7 +24,7 @@ import (
) )
var ( var (
StepBytes4 = crypto.Keccak256([]byte("Step(bytes,bytes)"))[:4] StepBytes4 = crypto.Keccak256([]byte("step(bytes,bytes)"))[:4]
CheatBytes4 = crypto.Keccak256([]byte("cheat(uint256,bytes32,bytes32,uint256)"))[:4] CheatBytes4 = crypto.Keccak256([]byte("cheat(uint256,bytes32,bytes32,uint256)"))[:4]
LoadKeccak256PreimagePartBytes4 = crypto.Keccak256([]byte("loadKeccak256PreimagePart(uint256,bytes)"))[:4] LoadKeccak256PreimagePartBytes4 = crypto.Keccak256([]byte("loadKeccak256PreimagePart(uint256,bytes)"))[:4]
) )
......
package api package api
import ( import (
"math/big"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
...@@ -25,7 +26,7 @@ func (mbv *MockBridgeView) DepositsByAddress(address common.Address) ([]*databas ...@@ -25,7 +26,7 @@ func (mbv *MockBridgeView) DepositsByAddress(address common.Address) ([]*databas
{ {
Deposit: database.Deposit{ Deposit: database.Deposit{
GUID: uuid.MustParse(guid1), GUID: uuid.MustParse(guid1),
InitiatedL1EventGUID: guid2, InitiatedL1EventGUID: uuid.MustParse(guid2),
Tx: database.Transaction{}, Tx: database.Transaction{},
TokenPair: database.TokenPair{}, TokenPair: database.TokenPair{},
}, },
...@@ -34,13 +35,28 @@ func (mbv *MockBridgeView) DepositsByAddress(address common.Address) ([]*databas ...@@ -34,13 +35,28 @@ func (mbv *MockBridgeView) DepositsByAddress(address common.Address) ([]*databas
}, nil }, nil
} }
// DepositsByAddress mocks returning deposits by an address
func (mbv *MockBridgeView) DepositByMessageNonce(nonce *big.Int) (*database.Deposit, error) {
return &database.Deposit{
GUID: uuid.MustParse(guid1),
InitiatedL1EventGUID: uuid.MustParse(guid2),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
}, nil
}
// LatestDepositMessageNonce mocks returning the latest cross domain message nonce for a deposit
func (mbv *MockBridgeView) LatestDepositMessageNonce() (*big.Int, error) {
return big.NewInt(0), nil
}
// WithdrawalsByAddress mocks returning withdrawals by an address // WithdrawalsByAddress mocks returning withdrawals by an address
func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*database.WithdrawalWithTransactionHashes, error) { func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*database.WithdrawalWithTransactionHashes, error) {
return []*database.WithdrawalWithTransactionHashes{ return []*database.WithdrawalWithTransactionHashes{
{ {
Withdrawal: database.Withdrawal{ Withdrawal: database.Withdrawal{
GUID: uuid.MustParse(guid2), GUID: uuid.MustParse(guid2),
InitiatedL2EventGUID: guid1, InitiatedL2EventGUID: uuid.MustParse(guid1),
WithdrawalHash: common.HexToHash("0x456"), WithdrawalHash: common.HexToHash("0x456"),
Tx: database.Transaction{}, Tx: database.Transaction{},
TokenPair: database.TokenPair{}, TokenPair: database.TokenPair{},
...@@ -50,6 +66,33 @@ func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*data ...@@ -50,6 +66,33 @@ func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*data
}, nil }, nil
} }
// WithdrawalsByMessageNonce mocks returning withdrawals by a withdrawal hash
func (mbv *MockBridgeView) WithdrawalByMessageNonce(nonce *big.Int) (*database.Withdrawal, error) {
return &database.Withdrawal{
GUID: uuid.MustParse(guid2),
InitiatedL2EventGUID: uuid.MustParse(guid1),
WithdrawalHash: common.HexToHash("0x456"),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
}, nil
}
// WithdrawalsByHash mocks returning withdrawals by a withdrawal hash
func (mbv *MockBridgeView) WithdrawalByHash(address common.Hash) (*database.Withdrawal, error) {
return &database.Withdrawal{
GUID: uuid.MustParse(guid2),
InitiatedL2EventGUID: uuid.MustParse(guid1),
WithdrawalHash: common.HexToHash("0x456"),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
}, nil
}
// LatestWithdrawalMessageNonce mocks returning the latest cross domain message nonce for a withdrawal
func (mbv *MockBridgeView) LatestWithdrawalMessageNonce() (*big.Int, error) {
return big.NewInt(0), nil
}
func TestHealthz(t *testing.T) { func TestHealthz(t *testing.T) {
api := NewApi(&MockBridgeView{}) api := NewApi(&MockBridgeView{})
request, err := http.NewRequest("GET", "/healthz", nil) request, err := http.NewRequest("GET", "/healthz", nil)
......
...@@ -5,6 +5,8 @@ import ( ...@@ -5,6 +5,8 @@ import (
"errors" "errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm" "gorm.io/gorm"
...@@ -21,6 +23,15 @@ type BlockHeader struct { ...@@ -21,6 +23,15 @@ type BlockHeader struct {
Timestamp uint64 Timestamp uint64
} }
func BlockHeaderFromGethHeader(header *types.Header) BlockHeader {
return BlockHeader{
Hash: header.Hash(),
ParentHash: header.ParentHash,
Number: U256{Int: header.Number},
Timestamp: header.Time,
}
}
type L1BlockHeader struct { type L1BlockHeader struct {
BlockHeader BlockHeader
} }
...@@ -41,8 +52,9 @@ type LegacyStateBatch struct { ...@@ -41,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
} }
......
...@@ -2,6 +2,8 @@ package database ...@@ -2,6 +2,8 @@ package database
import ( import (
"errors" "errors"
"fmt"
"math/big"
"gorm.io/gorm" "gorm.io/gorm"
...@@ -30,7 +32,17 @@ type TokenPair struct { ...@@ -30,7 +32,17 @@ type TokenPair struct {
type Deposit struct { type Deposit struct {
GUID uuid.UUID `gorm:"primaryKey"` GUID uuid.UUID `gorm:"primaryKey"`
InitiatedL1EventGUID string InitiatedL1EventGUID uuid.UUID
// Since we're only currently indexing a single StandardBridge,
// the message nonce serves as a unique identifier for this
// deposit. Once this generalizes to more than 1 deployed
// bridge, we need to include the `CrossDomainMessenger` address
// such that the (messenger_addr, nonce) is the unique identifier
// for a bridge msg
SentMessageNonce U256
FinalizedL2EventGUID *uuid.UUID
Tx Transaction `gorm:"embedded"` Tx Transaction `gorm:"embedded"`
TokenPair TokenPair `gorm:"embedded"` TokenPair TokenPair `gorm:"embedded"`
...@@ -43,11 +55,19 @@ type DepositWithTransactionHash struct { ...@@ -43,11 +55,19 @@ type DepositWithTransactionHash struct {
type Withdrawal struct { type Withdrawal struct {
GUID uuid.UUID `gorm:"primaryKey"` GUID uuid.UUID `gorm:"primaryKey"`
InitiatedL2EventGUID string InitiatedL2EventGUID uuid.UUID
// Since we're only currently indexing a single StandardBridge,
// the message nonce serves as a unique identifier for this
// withdrawal. Once this generalizes to more than 1 deployed
// bridge, we need to include the `CrossDomainMessenger` address
// such that the (messenger_addr, nonce) is the unique identifier
// for a bridge msg
SentMessageNonce U256
WithdrawalHash common.Hash `gorm:"serializer:json"` WithdrawalHash common.Hash `gorm:"serializer:json"`
ProvenL1EventGUID *string ProvenL1EventGUID *uuid.UUID
FinalizedL1EventGUID *string FinalizedL1EventGUID *uuid.UUID
Tx Transaction `gorm:"embedded"` Tx Transaction `gorm:"embedded"`
TokenPair TokenPair `gorm:"embedded"` TokenPair TokenPair `gorm:"embedded"`
...@@ -63,16 +83,24 @@ type WithdrawalWithTransactionHashes struct { ...@@ -63,16 +83,24 @@ type WithdrawalWithTransactionHashes struct {
type BridgeView interface { type BridgeView interface {
DepositsByAddress(address common.Address) ([]*DepositWithTransactionHash, error) DepositsByAddress(address common.Address) ([]*DepositWithTransactionHash, error)
DepositByMessageNonce(*big.Int) (*Deposit, error)
LatestDepositMessageNonce() (*big.Int, error)
WithdrawalsByAddress(address common.Address) ([]*WithdrawalWithTransactionHashes, error) WithdrawalsByAddress(address common.Address) ([]*WithdrawalWithTransactionHashes, error)
WithdrawalByMessageNonce(*big.Int) (*Withdrawal, error)
WithdrawalByHash(common.Hash) (*Withdrawal, error)
LatestWithdrawalMessageNonce() (*big.Int, error)
} }
type BridgeDB interface { type BridgeDB interface {
BridgeView BridgeView
StoreDeposits([]*Deposit) error StoreDeposits([]*Deposit) error
MarkFinalizedDepositEvent(uuid.UUID, uuid.UUID) error
StoreWithdrawals([]*Withdrawal) error StoreWithdrawals([]*Withdrawal) error
MarkProvenWithdrawalEvent(string, string) error MarkProvenWithdrawalEvent(uuid.UUID, uuid.UUID) error
MarkFinalizedWithdrawalEvent(string, string) error MarkFinalizedWithdrawalEvent(uuid.UUID, uuid.UUID) error
} }
/** /**
...@@ -114,6 +142,46 @@ func (db *bridgeDB) DepositsByAddress(address common.Address) ([]*DepositWithTra ...@@ -114,6 +142,46 @@ func (db *bridgeDB) DepositsByAddress(address common.Address) ([]*DepositWithTra
return deposits, nil return deposits, nil
} }
func (db *bridgeDB) DepositByMessageNonce(nonce *big.Int) (*Deposit, error) {
var deposit Deposit
result := db.gorm.First(&deposit, "sent_message_nonce = ?", U256{Int: nonce})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &deposit, nil
}
func (db *bridgeDB) LatestDepositMessageNonce() (*big.Int, error) {
var deposit Deposit
result := db.gorm.Order("sent_message_nonce DESC").Take(&deposit)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return deposit.SentMessageNonce.Int, nil
}
func (db *bridgeDB) MarkFinalizedDepositEvent(guid, finalizationEventGUID uuid.UUID) error {
var deposit Deposit
result := db.gorm.First(&deposit, "guid = ?", guid)
if result.Error != nil {
return result.Error
}
deposit.FinalizedL2EventGUID = &finalizationEventGUID
result = db.gorm.Save(&deposit)
return result.Error
}
// Withdrawals // Withdrawals
func (db *bridgeDB) StoreWithdrawals(withdrawals []*Withdrawal) error { func (db *bridgeDB) StoreWithdrawals(withdrawals []*Withdrawal) error {
...@@ -121,7 +189,7 @@ func (db *bridgeDB) StoreWithdrawals(withdrawals []*Withdrawal) error { ...@@ -121,7 +189,7 @@ func (db *bridgeDB) StoreWithdrawals(withdrawals []*Withdrawal) error {
return result.Error return result.Error
} }
func (db *bridgeDB) MarkProvenWithdrawalEvent(guid, provenL1EventGuid string) error { func (db *bridgeDB) MarkProvenWithdrawalEvent(guid, provenL1EventGuid uuid.UUID) error {
var withdrawal Withdrawal var withdrawal Withdrawal
result := db.gorm.First(&withdrawal, "guid = ?", guid) result := db.gorm.First(&withdrawal, "guid = ?", guid)
if result.Error != nil { if result.Error != nil {
...@@ -133,13 +201,17 @@ func (db *bridgeDB) MarkProvenWithdrawalEvent(guid, provenL1EventGuid string) er ...@@ -133,13 +201,17 @@ func (db *bridgeDB) MarkProvenWithdrawalEvent(guid, provenL1EventGuid string) er
return result.Error return result.Error
} }
func (db *bridgeDB) MarkFinalizedWithdrawalEvent(guid, finalizedL1EventGuid string) error { func (db *bridgeDB) MarkFinalizedWithdrawalEvent(guid, finalizedL1EventGuid uuid.UUID) error {
var withdrawal Withdrawal var withdrawal Withdrawal
result := db.gorm.First(&withdrawal, "guid = ?", guid) result := db.gorm.First(&withdrawal, "guid = ?", guid)
if result.Error != nil { if result.Error != nil {
return result.Error return result.Error
} }
if withdrawal.ProvenL1EventGUID == nil {
return fmt.Errorf("withdrawal %s marked finalized prior to being proven", guid)
}
withdrawal.FinalizedL1EventGUID = &finalizedL1EventGuid withdrawal.FinalizedL1EventGUID = &finalizedL1EventGuid
result = db.gorm.Save(&withdrawal) result = db.gorm.Save(&withdrawal)
return result.Error return result.Error
...@@ -167,3 +239,45 @@ func (db *bridgeDB) WithdrawalsByAddress(address common.Address) ([]*WithdrawalW ...@@ -167,3 +239,45 @@ func (db *bridgeDB) WithdrawalsByAddress(address common.Address) ([]*WithdrawalW
return withdrawals, nil return withdrawals, nil
} }
func (db *bridgeDB) WithdrawalByMessageNonce(nonce *big.Int) (*Withdrawal, error) {
var withdrawal Withdrawal
result := db.gorm.First(&withdrawal, "sent_message_nonce = ?", U256{Int: nonce})
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &withdrawal, nil
}
func (db *bridgeDB) WithdrawalByHash(hash common.Hash) (*Withdrawal, error) {
var withdrawal Withdrawal
result := db.gorm.First(&withdrawal, "withdrawal_hash = ?", hash.String())
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &withdrawal, nil
}
func (db *bridgeDB) LatestWithdrawalMessageNonce() (*big.Int, error) {
var withdrawal Withdrawal
result := db.gorm.Order("sent_message_nonce DESC").Take(&withdrawal)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return withdrawal.SentMessageNonce.Int, nil
}
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/google/uuid" "github.com/google/uuid"
) )
...@@ -22,6 +23,20 @@ type ContractEvent struct { ...@@ -22,6 +23,20 @@ type ContractEvent struct {
Timestamp uint64 Timestamp uint64
} }
func ContractEventFromGethLog(log *types.Log, timestamp uint64) ContractEvent {
return ContractEvent{
GUID: uuid.New(),
BlockHash: log.BlockHash,
TransactionHash: log.TxHash,
EventSignature: log.Topics[0],
LogIndex: uint64(log.Index),
Timestamp: timestamp,
}
}
type L1ContractEvent struct { type L1ContractEvent struct {
ContractEvent `gorm:"embedded"` ContractEvent `gorm:"embedded"`
} }
......
...@@ -10,14 +10,15 @@ CREATE TABLE IF NOT EXISTS l1_block_headers ( ...@@ -10,14 +10,15 @@ CREATE TABLE IF NOT EXISTS l1_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 timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
CREATE TABLE IF NOT EXISTS l2_block_headers ( CREATE TABLE IF NOT EXISTS l2_block_headers (
hash VARCHAR NOT NULL PRIMARY KEY, -- Block header
parent_hash VARCHAR NOT NULL, hash VARCHAR NOT NULL PRIMARY KEY,
number UINT256, parent_hash VARCHAR NOT NULL,
timestamp INTEGER NOT NULL number UINT256,
timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
/** /**
...@@ -30,7 +31,7 @@ CREATE TABLE IF NOT EXISTS l1_contract_events ( ...@@ -30,7 +31,7 @@ CREATE TABLE IF NOT EXISTS l1_contract_events (
transaction_hash VARCHAR NOT NULL, transaction_hash VARCHAR NOT NULL,
event_signature VARCHAR NOT NULL, event_signature VARCHAR NOT NULL,
log_index INTEGER NOT NULL, log_index INTEGER NOT NULL,
timestamp INTEGER NOT NULL timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
CREATE TABLE IF NOT EXISTS l2_contract_events ( CREATE TABLE IF NOT EXISTS l2_contract_events (
...@@ -39,7 +40,7 @@ CREATE TABLE IF NOT EXISTS l2_contract_events ( ...@@ -39,7 +40,7 @@ CREATE TABLE IF NOT EXISTS l2_contract_events (
transaction_hash VARCHAR NOT NULL, transaction_hash VARCHAR NOT NULL,
event_signature VARCHAR NOT NULL, event_signature VARCHAR NOT NULL,
log_index INTEGER NOT NULL, log_index INTEGER NOT NULL,
timestamp INTEGER NOT NULL timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
-- Tables that index finalization markers for L2 blocks. -- Tables that index finalization markers for L2 blocks.
...@@ -69,6 +70,10 @@ CREATE TABLE IF NOT EXISTS deposits ( ...@@ -69,6 +70,10 @@ 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 UNIQUE,
-- Finalization marker for the deposit
finalized_l2_event_guid VARCHAR REFERENCES l2_contract_events(guid),
-- Deposit information (do we need indexes on from/to?) -- Deposit information (do we need indexes on from/to?)
from_address VARCHAR NOT NULL, from_address VARCHAR NOT NULL,
...@@ -78,7 +83,7 @@ CREATE TABLE IF NOT EXISTS deposits ( ...@@ -78,7 +83,7 @@ CREATE TABLE IF NOT EXISTS deposits (
l2_token_address VARCHAR NOT NULL, l2_token_address VARCHAR NOT NULL,
amount UINT256, amount UINT256,
data VARCHAR NOT NULL, data VARCHAR NOT NULL,
timestamp INTEGER NOT NULL timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
CREATE TABLE IF NOT EXISTS withdrawals ( CREATE TABLE IF NOT EXISTS withdrawals (
...@@ -86,6 +91,7 @@ CREATE TABLE IF NOT EXISTS withdrawals ( ...@@ -86,6 +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 UNIQUE,
-- Multistep (bedrock) process of a withdrawal -- Multistep (bedrock) process of a withdrawal
withdrawal_hash VARCHAR NOT NULL, withdrawal_hash VARCHAR NOT NULL,
...@@ -101,5 +107,5 @@ CREATE TABLE IF NOT EXISTS withdrawals ( ...@@ -101,5 +107,5 @@ CREATE TABLE IF NOT EXISTS withdrawals (
l2_token_address VARCHAR NOT NULL, l2_token_address VARCHAR NOT NULL,
amount UINT256, amount UINT256,
data VARCHAR NOT NULL, data VARCHAR NOT NULL,
timestamp INTEGER NOT NULL timestamp INTEGER NOT NULL CHECK (timestamp > 0)
); );
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 ProcessedContractEventLogIndexKey struct {
header common.Hash
index uint
}
type ProcessedContractEvents struct {
events []*database.ContractEvent
eventsBySignature map[common.Hash][]*database.ContractEvent
eventByLogIndex map[ProcessedContractEventLogIndexKey]*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[ProcessedContractEventLogIndexKey]*database.ContractEvent),
eventLog: make(map[uuid.UUID]*types.Log),
}
}
func (p *ProcessedContractEvents) AddLog(log *types.Log, time uint64) *database.ContractEvent {
contractEvent := database.ContractEventFromGethLog(log, time)
p.events = append(p.events, &contractEvent)
p.eventsBySignature[contractEvent.EventSignature] = append(p.eventsBySignature[contractEvent.EventSignature], &contractEvent)
p.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index}] = &contractEvent
p.eventLog[contractEvent.GUID] = log
return &contractEvent
}
func UnpackLog(out interface{}, log *types.Log, name string, contractAbi *abi.ABI) error {
eventAbi, ok := contractAbi.Events[name]
if !ok {
return fmt.Errorf("event %s not present in supplied ABI", name)
} else if len(log.Topics) == 0 {
return errors.New("anonymous events are not supported")
} else if log.Topics[0] != eventAbi.ID {
return errors.New("event signature mismatch")
}
err := contractAbi.UnpackIntoInterface(out, name, log.Data)
if err != nil {
return 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 omitted
err := abi.ParseTopics(out, indexedArgs, log.Topics[1:])
if err != nil {
return err
}
}
return nil
}
This diff is collapsed.
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"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/google/uuid"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
...@@ -124,8 +125,8 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -124,8 +125,8 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2
return err return err
} }
numLogs := len(logs) l2ContractEvents := make([]*database.L2ContractEvent, len(logs))
l2ContractEvents := make([]*database.L2ContractEvent, numLogs) processedContractEvents := NewProcessedContractEvents()
for i, log := range logs { for i, log := range logs {
header, ok := l2HeaderMap[log.BlockHash] header, ok := l2HeaderMap[log.BlockHash]
if !ok { if !ok {
...@@ -133,16 +134,8 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -133,16 +134,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")
} }
l2ContractEvents[i] = &database.L2ContractEvent{ contractEvent := processedContractEvents.AddLog(&logs[i], header.Time)
ContractEvent: database.ContractEvent{ l2ContractEvents[i] = &database.L2ContractEvent{ContractEvent: *contractEvent}
GUID: uuid.New(),
BlockHash: log.BlockHash,
TransactionHash: log.TxHash,
EventSignature: log.Topics[0],
LogIndex: uint64(log.Index),
Timestamp: header.Time,
},
}
} }
/** Update Database **/ /** Update Database **/
...@@ -153,15 +146,118 @@ func l2ProcessFn(processLog log.Logger, ethClient node.EthClient, l2Contracts L2 ...@@ -153,15 +146,118 @@ 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)
if err != nil { if err != nil {
return err return err
} }
// forward along contract events to the bridge processor
err = l2BridgeProcessContractEvents(processLog, db, ethClient, processedContractEvents)
if err != nil {
return err
}
} }
// a-ok! // a-ok!
return nil return nil
} }
} }
func l2BridgeProcessContractEvents(processLog log.Logger, db *database.DB, ethClient node.EthClient, events *ProcessedContractEvents) error {
rawEthClient := ethclient.NewClient(ethClient.RawRpcClient())
l2ToL1MessagePasserABI, err := bindings.L2ToL1MessagePasserMetaData.GetAbi()
if err != nil {
return err
}
messagePassedEventAbi := l2ToL1MessagePasserABI.Events["MessagePassed"]
// Process New Withdrawals
initiatedWithdrawalEvents, err := StandardBridgeInitiatedEvents(events)
if err != nil {
return err
}
withdrawals := make([]*database.Withdrawal, len(initiatedWithdrawalEvents))
for i, initiatedBridgeEvent := range initiatedWithdrawalEvents {
log := events.eventLog[initiatedBridgeEvent.RawEvent.GUID]
// extract the withdrawal hash from the MessagePassed event
var msgPassedData bindings.L2ToL1MessagePasserMessagePassed
msgPassedLog := events.eventLog[events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 1}].GUID]
err := UnpackLog(&msgPassedData, msgPassedLog, messagePassedEventAbi.Name, l2ToL1MessagePasserABI)
if err != nil {
return err
}
withdrawals[i] = &database.Withdrawal{
GUID: uuid.New(),
InitiatedL2EventGUID: initiatedBridgeEvent.RawEvent.GUID,
SentMessageNonce: database.U256{Int: initiatedBridgeEvent.CrossDomainMessengerNonce},
WithdrawalHash: msgPassedData.WithdrawalHash,
TokenPair: database.TokenPair{L1TokenAddress: initiatedBridgeEvent.LocalToken, L2TokenAddress: initiatedBridgeEvent.RemoteToken},
Tx: database.Transaction{
FromAddress: initiatedBridgeEvent.From,
ToAddress: initiatedBridgeEvent.To,
Amount: database.U256{Int: initiatedBridgeEvent.Amount},
Data: initiatedBridgeEvent.ExtraData,
Timestamp: initiatedBridgeEvent.RawEvent.Timestamp,
},
}
}
if len(withdrawals) > 0 {
processLog.Info("detected L2StandardBridge withdrawals", "num", len(withdrawals))
err := db.Bridge.StoreWithdrawals(withdrawals)
if err != nil {
return err
}
}
// Finalize Deposits
finalizationBridgeEvents, err := StandardBridgeFinalizedEvents(rawEthClient, events)
if err != nil {
return err
}
for _, finalizedBridgeEvent := range finalizationBridgeEvents {
nonce := finalizedBridgeEvent.CrossDomainMessengerNonce
deposit, err := db.Bridge.DepositByMessageNonce(nonce)
if err != nil {
processLog.Error("error querying associated deposit messsage using nonce", "cross_domain_messenger_nonce", nonce)
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
if latestNonce == nil || nonce.Cmp(latestNonce) > 0 {
processLog.Warn("behind on indexed L1 deposits")
return errors.New("waiting for L1Processor to catch up")
} else {
processLog.Crit("missing indexed deposit for this finalization event")
return errors.New("missing deposit message")
}
}
err = db.Bridge.MarkFinalizedDepositEvent(deposit.GUID, finalizedBridgeEvent.RawEvent.GUID)
if err != nil {
processLog.Error("error finalizing deposit", "err", err)
return err
}
}
if len(finalizationBridgeEvents) > 0 {
processLog.Info("finalized L1StandardBridge deposits", "size", len(finalizationBridgeEvents))
}
// a-ok
return nil
}
package processor
import (
"context"
"math/big"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
type OptimismPortalWithdrawalProvenEvent struct {
*bindings.OptimismPortalWithdrawalProven
RawEvent *database.ContractEvent
}
type OptimismPortalProvenWithdrawal struct {
OutputRoot [32]byte
Timestamp *big.Int
L2OutputIndex *big.Int
}
func OptimismPortalWithdrawalProvenEvents(events *ProcessedContractEvents) ([]OptimismPortalWithdrawalProvenEvent, error) {
optimismPortalAbi, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil {
return nil, err
}
processedWithdrawalProvenEvents := events.eventsBySignature[optimismPortalAbi.Events["WithdrawalProven"].ID]
provenEvents := make([]OptimismPortalWithdrawalProvenEvent, len(processedWithdrawalProvenEvents))
for i, provenEvent := range processedWithdrawalProvenEvents {
provenEvents[i] = OptimismPortalWithdrawalProvenEvent{nil, provenEvent}
}
return provenEvents, nil
}
func OptimismPortalQueryProvenWithdrawal(ethClient *ethclient.Client, portalAddress common.Address, withdrawalHash common.Hash) (OptimismPortalProvenWithdrawal, error) {
var provenWithdrawal OptimismPortalProvenWithdrawal
optimismPortalAbi, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil {
return provenWithdrawal, err
}
name := "provenWithdrawals"
txData, err := optimismPortalAbi.Pack(name, withdrawalHash)
if err != nil {
return provenWithdrawal, err
}
callMsg := ethereum.CallMsg{To: &portalAddress, Data: txData}
data, err := ethClient.CallContract(context.Background(), callMsg, nil)
if err != nil {
return provenWithdrawal, err
}
err = optimismPortalAbi.UnpackIntoInterface(&provenWithdrawal, name, data)
if err != nil {
return provenWithdrawal, err
}
return provenWithdrawal, nil
}
...@@ -55,8 +55,8 @@ func (p processor) Start() { ...@@ -55,8 +55,8 @@ func (p processor) Start() {
firstHeader := unprocessedHeaders[0] firstHeader := unprocessedHeaders[0]
lastHeader := unprocessedHeaders[len(unprocessedHeaders)-1] lastHeader := unprocessedHeaders[len(unprocessedHeaders)-1]
batchLog := p.processLog.New("batch_start_block_number", firstHeader.Number, "batch_end_block_number", lastHeader.Number) batchLog := p.processLog.New("batch_start_block_number", firstHeader.Number, "batch_end_block_number", lastHeader.Number)
batchLog.Info("processing batch")
err := p.db.Transaction(func(db *database.DB) error { err := p.db.Transaction(func(db *database.DB) error {
batchLog.Info("processing batch")
return p.processFn(db, unprocessedHeaders) return p.processFn(db, unprocessedHeaders)
}) })
......
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 {
// We hardcode to ERC20 since ETH can be pseudo-represented as an ERC20 utilizing
// the hardcoded ETH address
*bindings.L1StandardBridgeERC20BridgeInitiated
CrossDomainMessengerNonce *big.Int
RawEvent *database.ContractEvent
}
type StandardBridgeFinalizedEvent struct {
// We hardcode to ERC20 since ETH can be pseudo-represented as an ERC20 utilizing
// the hardcoded ETH address
*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) {
ethBridgeInitiatedEvents, err := _standardBridgeInitiatedEvents[bindings.L1StandardBridgeETHBridgeInitiated](events)
if err != nil {
return nil, err
}
erc20BridgeInitiatedEvents, err := _standardBridgeInitiatedEvents[bindings.L1StandardBridgeERC20BridgeInitiated](events)
if err != nil {
return nil, err
}
return append(ethBridgeInitiatedEvents, erc20BridgeInitiatedEvents...), 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) {
ethBridgeFinalizedEvents, err := _standardBridgeFinalizedEvents[bindings.L1StandardBridgeETHBridgeFinalized](rawEthClient, events)
if err != nil {
return nil, err
}
erc20BridgeFinalizedEvents, err := _standardBridgeFinalizedEvents[bindings.L1StandardBridgeERC20BridgeFinalized](rawEthClient, events)
if err != nil {
return nil, err
}
return append(ethBridgeFinalizedEvents, erc20BridgeFinalizedEvents...), nil
}
// parse out eth or erc20 bridge initiated events
func _standardBridgeInitiatedEvents[BridgeEvent bindings.L1StandardBridgeETHBridgeInitiated | bindings.L1StandardBridgeERC20BridgeInitiated](
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
}
sentMessageEventAbi := l1CrossDomainMessengerABI.Events["SentMessage"]
var tmp BridgeEvent
var eventName string
var finalizeMethodName string
switch any(tmp).(type) {
case bindings.L1StandardBridgeETHBridgeInitiated:
eventName = "ETHBridgeInitiated"
finalizeMethodName = "finalizeBridgeETH"
case bindings.L1StandardBridgeERC20BridgeInitiated:
eventName = "ERC20BridgeInitiated"
finalizeMethodName = "finalizeBridgeERC20"
default:
panic("should not be here")
}
processedInitiatedBridgeEvents := events.eventsBySignature[l1StandardBridgeABI.Events[eventName].ID]
initiatedBridgeEvents := make([]StandardBridgeInitiatedEvent, len(processedInitiatedBridgeEvents))
for i, bridgeInitiatedEvent := range processedInitiatedBridgeEvents {
log := events.eventLog[bridgeInitiatedEvent.GUID]
bridgeData := new(BridgeEvent)
err := UnpackLog(bridgeData, log, eventName, l1StandardBridgeABI)
if err != nil {
return nil, err
}
// Look for the sent message event to extract the associated messager nonce
// - L1: BridgeInitiated -> Portal#DepositTransaction -> SentMessage ...
// - L1: BridgeInitiated -> L2ToL1MessagePasser#MessagePassed -> SentMessage ...
var sentMsgData bindings.L1CrossDomainMessengerSentMessage
sentMsgLog := events.eventLog[events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, log.Index + 2}].GUID]
err = UnpackLog(&sentMsgData, sentMsgLog, sentMessageEventAbi.Name, l1CrossDomainMessengerABI)
if err != nil {
return nil, err
}
var erc20BridgeData *bindings.L1StandardBridgeERC20BridgeInitiated
var expectedCrossDomainMessage []byte
switch any(bridgeData).(type) {
case *bindings.L1StandardBridgeETHBridgeInitiated:
ethBridgeData := any(bridgeData).(*bindings.L1StandardBridgeETHBridgeInitiated)
expectedCrossDomainMessage, err = l1StandardBridgeABI.Pack(finalizeMethodName, ethBridgeData.From, ethBridgeData.To, ethBridgeData.Amount, ethBridgeData.ExtraData)
if err != nil {
return nil, err
}
// represent eth bridge as an erc20
erc20BridgeData = &bindings.L1StandardBridgeERC20BridgeInitiated{
// Represent ETH using the hardcoded address
LocalToken: ethAddress, RemoteToken: ethAddress,
// Bridge data
From: ethBridgeData.From, To: ethBridgeData.To, Amount: ethBridgeData.Amount, ExtraData: ethBridgeData.ExtraData,
}
case *bindings.L1StandardBridgeERC20BridgeInitiated:
_temp := any(bridgeData).(bindings.L1StandardBridgeERC20BridgeInitiated)
erc20BridgeData = &_temp
expectedCrossDomainMessage, err = l1StandardBridgeABI.Pack(finalizeMethodName, erc20BridgeData.RemoteToken, erc20BridgeData.LocalToken, erc20BridgeData.From, erc20BridgeData.To, erc20BridgeData.Amount, erc20BridgeData.ExtraData)
if err != nil {
return nil, err
}
}
if !bytes.Equal(sentMsgData.Message, expectedCrossDomainMessage) {
return nil, errors.New("bridge cross domain message mismatch")
}
initiatedBridgeEvents[i] = StandardBridgeInitiatedEvent{erc20BridgeData, sentMsgData.MessageNonce, bridgeInitiatedEvent}
}
return initiatedBridgeEvents, nil
}
// parse out eth or erc20 bridge finalization events
func _standardBridgeFinalizedEvents[BridgeEvent bindings.L1StandardBridgeETHBridgeFinalized | bindings.L1StandardBridgeERC20BridgeFinalized](
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
}
relayedMessageEventAbi := l1CrossDomainMessengerABI.Events["RelayedMessage"]
relayMessageMethodAbi := l1CrossDomainMessengerABI.Methods["relayMessage"]
var bridgeData BridgeEvent
var eventName string
switch any(bridgeData).(type) {
case bindings.L1StandardBridgeETHBridgeFinalized:
eventName = "ETHBridgeFinalized"
case bindings.L1StandardBridgeERC20BridgeFinalized:
eventName = "ERC20BridgeFinalized"
default:
panic("should not be here")
}
processedFinalizedBridgeEvents := events.eventsBySignature[l1StandardBridgeABI.Events[eventName].ID]
finalizedBridgeEvents := make([]StandardBridgeFinalizedEvent, len(processedFinalizedBridgeEvents))
for i, bridgeFinalizedEvent := range processedFinalizedBridgeEvents {
log := events.eventLog[bridgeFinalizedEvent.GUID]
var bridgeData BridgeEvent
err := UnpackLog(&bridgeData, log, eventName, l1StandardBridgeABI)
if err != nil {
return nil, err
}
// Look for the RelayedMessage event that follows right after the BridgeFinalized Event
relayedMsgLog := events.eventLog[events.eventByLogIndex[ProcessedContractEventLogIndexKey{log.BlockHash, 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")
}
var erc20BridgeData *bindings.L1StandardBridgeERC20BridgeFinalized
switch any(bridgeData).(type) {
case bindings.L1StandardBridgeETHBridgeInitiated:
ethBridgeData := any(bridgeData).(bindings.L1StandardBridgeETHBridgeFinalized)
erc20BridgeData = &bindings.L1StandardBridgeERC20BridgeFinalized{
// Represent ETH using the hardcoded address
LocalToken: ethAddress, RemoteToken: ethAddress,
// Bridge data
From: ethBridgeData.From, To: ethBridgeData.To, Amount: ethBridgeData.Amount, ExtraData: ethBridgeData.ExtraData,
}
case bindings.L1StandardBridgeERC20BridgeInitiated:
_temp := any(bridgeData).(bindings.L1StandardBridgeERC20BridgeFinalized)
erc20BridgeData = &_temp
}
finalizedBridgeEvents[i] = StandardBridgeFinalizedEvent{erc20BridgeData, nonce, bridgeFinalizedEvent}
}
return finalizedBridgeEvents, nil
}
...@@ -4,7 +4,10 @@ ...@@ -4,7 +4,10 @@
"defaultBase": "develop" "defaultBase": "develop"
}, },
"implicitDependencies": { "implicitDependencies": {
"nx.json": "*" "nx.json": "*",
"tsconfig.json": "*",
".foundryrc": "*",
".nvmrc": "*"
}, },
"tasksRunnerOptions": { "tasksRunnerOptions": {
"default": { "default": {
......
This diff is collapsed.
This diff is collapsed.
package fault
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
// setupFaultDisputeGame deploys the FaultDisputeGame contract to a simulated backend
func setupFaultDisputeGame() (common.Address, *bind.TransactOpts, *backends.SimulatedBackend, *bindings.FaultDisputeGame, error) {
privateKey, err := crypto.GenerateKey()
from := crypto.PubkeyToAddress(privateKey.PublicKey)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
opts, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337))
if err != nil {
return common.Address{}, nil, nil, nil, err
}
backend := backends.NewSimulatedBackend(core.GenesisAlloc{from: {Balance: big.NewInt(params.Ether)}}, 50_000_000)
_, _, contract, err := bindings.DeployFaultDisputeGame(
opts,
backend,
[32]byte{0x01},
big.NewInt(15),
common.Address{0xdd},
)
if err != nil {
return common.Address{}, nil, nil, nil, err
}
return from, opts, backend, contract, nil
}
// TestBuildFaultDefendData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultDefendData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
responder, _ := newTestFaultResponder(t, false)
data, err := responder.buildFaultDefendData(1, [32]byte{0x02, 0x03})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Defend(opts, big.NewInt(1), [32]byte{0x02, 0x03})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
// TestBuildFaultAttackData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultAttackData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
responder, _ := newTestFaultResponder(t, false)
data, err := responder.buildFaultAttackData(1, [32]byte{0x02, 0x03})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Attack(opts, big.NewInt(1), [32]byte{0x02, 0x03})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
// TestBuildFaultStepData ensures that the manual ABI packing is the same as going through the bound contract.
func TestBuildFaultStepData(t *testing.T) {
_, opts, _, contract, err := setupFaultDisputeGame()
require.NoError(t, err)
responder, _ := newTestFaultResponder(t, false)
data, err := responder.buildStepTxData(StepCallData{
ClaimIndex: 2,
IsAttack: false,
StateData: []byte{0x01},
Proof: []byte{0x02},
})
require.NoError(t, err)
opts.GasLimit = 100_000
tx, err := contract.Step(opts, big.NewInt(2), false, []byte{0x01}, []byte{0x02})
require.NoError(t, err)
require.Equal(t, data, tx.Data())
}
...@@ -2,53 +2,68 @@ package fault ...@@ -2,53 +2,68 @@ package fault
import ( import (
"context" "context"
"sync" "errors"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
type Agent struct { type Agent struct {
mu sync.Mutex
game Game
solver *Solver solver *Solver
trace TraceProvider trace TraceProvider
loader Loader
responder Responder responder Responder
maxDepth int maxDepth int
log log.Logger log log.Logger
} }
func NewAgent(game Game, maxDepth int, trace TraceProvider, responder Responder, log log.Logger) Agent { func NewAgent(loader Loader, maxDepth int, trace TraceProvider, responder Responder, log log.Logger) Agent {
return Agent{ return Agent{
game: game,
solver: NewSolver(maxDepth, trace), solver: NewSolver(maxDepth, trace),
trace: trace, trace: trace,
loader: loader,
responder: responder, responder: responder,
maxDepth: maxDepth, maxDepth: maxDepth,
log: log, log: log,
} }
} }
// AddClaim stores a claim in the local state. // Act iterates the game & performs all of the next actions.
// This function shares a lock with PerformActions. func (a *Agent) Act() error {
func (a *Agent) AddClaim(claim Claim) error { game, err := a.newGameFromContracts(context.Background())
a.mu.Lock() if err != nil {
defer a.mu.Unlock() a.log.Error("Failed to create new game", "err", err)
return a.game.Put(claim) return err
}
// Create counter claims
for _, claim := range game.Claims() {
_ = a.move(claim, game)
}
// Step on all leaf claims
for _, claim := range game.Claims() {
_ = a.step(claim, game)
}
return nil
} }
// PerformActions iterates the game & performs all of the next actions. // newGameFromContracts initializes a new game state from the state in the contract
// Note: PerformActions & AddClaim share a lock so the responder cannot func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) {
// call AddClaim on the same thread. claims, err := a.loader.FetchClaims(ctx)
func (a *Agent) PerformActions() { if err != nil {
a.mu.Lock() return nil, err
defer a.mu.Unlock() }
for _, claim := range a.game.Claims() { if len(claims) == 0 {
_ = a.move(claim) return nil, errors.New("no claims")
} }
game := NewGameState(claims[0], uint64(a.maxDepth))
if err := game.PutAll(claims[1:]); err != nil {
return nil, err
}
return game, nil
} }
// move determines & executes the next move given a claim pair // move determines & executes the next move given a claim pair
func (a *Agent) move(claim Claim) error { func (a *Agent) move(claim Claim, game Game) error {
a.log.Info("Fetching claims")
nextMove, err := a.solver.NextMove(claim) nextMove, err := a.solver.NextMove(claim)
if err != nil { if err != nil {
a.log.Warn("Failed to execute the next move", "err", err) a.log.Warn("Failed to execute the next move", "err", err)
...@@ -62,10 +77,33 @@ func (a *Agent) move(claim Claim) error { ...@@ -62,10 +77,33 @@ func (a *Agent) move(claim Claim) error {
log := a.log.New("is_defend", move.DefendsParent(), "depth", move.Depth(), "index_at_depth", move.IndexAtDepth(), "value", move.Value, log := a.log.New("is_defend", move.DefendsParent(), "depth", move.Depth(), "index_at_depth", move.IndexAtDepth(), "value", move.Value,
"letter", string(move.Value[31:]), "trace_index", move.Value[30], "letter", string(move.Value[31:]), "trace_index", move.Value[30],
"parent_letter", string(claim.Value[31:]), "parent_trace_index", claim.Value[30]) "parent_letter", string(claim.Value[31:]), "parent_trace_index", claim.Value[30])
if a.game.IsDuplicate(move) { if game.IsDuplicate(move) {
log.Debug("Duplicate move") log.Debug("Duplicate move")
return nil return nil
} }
log.Info("Performing move") log.Info("Performing move")
return a.responder.Respond(context.TODO(), move) return a.responder.Respond(context.TODO(), move)
} }
// step attempts to execute the step through the responder
func (a *Agent) step(claim Claim, game Game) error {
if claim.Depth() != a.maxDepth {
return nil
}
a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
step, err := a.solver.AttemptStep(claim)
if err != nil {
a.log.Info("Failed to get a step", "err", err)
return err
}
a.log.Info("Performing step",
"depth", step.LeafClaim.Depth(), "index_at_depth", step.LeafClaim.IndexAtDepth(), "value", step.LeafClaim.Value,
"is_attack", step.IsAttack)
callData := StepCallData{
ClaimIndex: uint64(step.LeafClaim.ContractIndex),
IsAttack: step.IsAttack,
}
return a.responder.Step(context.TODO(), callData)
}
...@@ -2,74 +2,74 @@ package fault ...@@ -2,74 +2,74 @@ package fault
import ( import (
"context" "context"
"os"
"time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
type Orchestrator struct { type Orchestrator struct {
agents []Agent agents []Agent
outputChs []chan Claim claims []Claim
responses chan Claim steps []StepCallData
// tracking when to exit
claimLen, stepLen, step int
} }
func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, root Claim) Orchestrator { func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, root Claim) Orchestrator {
o := Orchestrator{ o := Orchestrator{
responses: make(chan Claim, 100), agents: make([]Agent, len(traces)),
outputChs: make([]chan Claim, len(traces)), claims: []Claim{root},
agents: make([]Agent, len(traces)), steps: make([]StepCallData, 0),
} }
log.Info("Starting game", "root_letter", string(root.Value[31:])) log.Info("Starting game", "root_letter", string(root.Value[31:]))
for i, trace := range traces { for i, trace := range traces {
game := NewGameState(root, maxDepth) o.agents[i] = NewAgent(&o, int(maxDepth), trace, &o, log.New("role", names[i]))
o.agents[i] = NewAgent(game, int(maxDepth), trace, &o, log.New("role", names[i]))
o.outputChs[i] = make(chan Claim)
} }
return o return o
} }
func (o *Orchestrator) Respond(_ context.Context, response Claim) error { func (o *Orchestrator) Respond(_ context.Context, response Claim) error {
o.responses <- response response.ContractIndex = len(o.claims)
o.claims = append(o.claims, response)
return nil return nil
} }
func (o *Orchestrator) Step(ctx context.Context, stepData StepCallData) error { func (o *Orchestrator) Step(_ context.Context, stepData StepCallData) error {
log.Info("Step recorded", "step", stepData)
o.steps = append(o.steps, stepData)
return nil return nil
} }
func (o *Orchestrator) Start() { func (o *Orchestrator) FetchClaims(ctx context.Context) ([]Claim, error) {
for i := 0; i < len(o.agents); i++ { c := make([]Claim, len(o.claims))
go runAgent(&o.agents[i], o.outputChs[i]) copy(c, o.claims)
} return c, nil
o.responderThread()
} }
func runAgent(agent *Agent, claimCh <-chan Claim) { func (o *Orchestrator) Start() {
for { for {
agent.PerformActions() for _, a := range o.agents {
// Note: Should drain the channel here _ = a.Act()
claim := <-claimCh }
_ = agent.AddClaim(claim) if o.shouldExit() {
log.Info("exiting")
return
}
} }
} }
func (o *Orchestrator) responderThread() { func (o *Orchestrator) shouldExit() bool {
timer := time.NewTimer(200 * time.Millisecond) cl := o.claimLen
defer timer.Stop() sl := o.stepLen
for {
select { o.claimLen = len(o.claims)
case resp := <-o.responses: o.stepLen = len(o.steps)
timer.Reset(200 * time.Millisecond)
for _, ch := range o.outputChs {
// Copy it. Should be immutable, but be sure.
resp := resp
ch <- resp
}
case <-timer.C:
os.Exit(0)
}
noProgress := o.claimLen == cl && o.stepLen == sl
if noProgress {
o.step = o.step + 1
} else {
o.step = 0
} }
return noProgress && o.step == 1
} }
...@@ -104,7 +104,6 @@ func (r *faultResponder) sendTxAndWait(ctx context.Context, txData []byte) error ...@@ -104,7 +104,6 @@ func (r *faultResponder) sendTxAndWait(ctx context.Context, txData []byte) error
func (r *faultResponder) buildStepTxData(stepData StepCallData) ([]byte, error) { func (r *faultResponder) buildStepTxData(stepData StepCallData) ([]byte, error) {
return r.fdgAbi.Pack( return r.fdgAbi.Pack(
"step", "step",
big.NewInt(int64(stepData.StateIndex)),
big.NewInt(int64(stepData.ClaimIndex)), big.NewInt(int64(stepData.ClaimIndex)),
stepData.IsAttack, stepData.IsAttack,
stepData.StateData, stepData.StateData,
......
...@@ -31,14 +31,13 @@ func (s *Solver) NextMove(claim Claim) (*Claim, error) { ...@@ -31,14 +31,13 @@ func (s *Solver) NextMove(claim Claim) (*Claim, error) {
} }
type StepData struct { type StepData struct {
LeafClaim Claim LeafClaim Claim
StateClaim Claim IsAttack bool
IsAttack bool
} }
// AttemptStep determines what step should occur for a given leaf claim. // AttemptStep determines what step should occur for a given leaf claim.
// An error will be returned if the claim is not at the max depth. // An error will be returned if the claim is not at the max depth.
func (s *Solver) AttemptStep(claim Claim, state Game) (StepData, error) { func (s *Solver) AttemptStep(claim Claim) (StepData, error) {
if claim.Depth() != s.gameDepth { if claim.Depth() != s.gameDepth {
return StepData{}, errors.New("cannot step on non-leaf claims") return StepData{}, errors.New("cannot step on non-leaf claims")
} }
...@@ -46,20 +45,9 @@ func (s *Solver) AttemptStep(claim Claim, state Game) (StepData, error) { ...@@ -46,20 +45,9 @@ func (s *Solver) AttemptStep(claim Claim, state Game) (StepData, error) {
if err != nil { if err != nil {
return StepData{}, err return StepData{}, err
} }
var selectorFn func(Claim) (Claim, error)
if claimCorrect {
selectorFn = state.PostStateClaim
} else {
selectorFn = state.PreStateClaim
}
stateClaim, err := selectorFn(claim)
if err != nil {
return StepData{}, err
}
return StepData{ return StepData{
LeafClaim: claim, LeafClaim: claim,
StateClaim: stateClaim, IsAttack: claimCorrect,
IsAttack: claimCorrect,
}, nil }, nil
} }
......
...@@ -89,12 +89,11 @@ func TestAttemptStep(t *testing.T) { ...@@ -89,12 +89,11 @@ func TestAttemptStep(t *testing.T) {
require.NoError(t, g.Put(middle)) require.NoError(t, g.Put(middle))
require.NoError(t, g.Put(bottom)) require.NoError(t, g.Put(bottom))
step, err := solver.AttemptStep(bottom, g) step, err := solver.AttemptStep(bottom)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, bottom, step.LeafClaim) require.Equal(t, bottom, step.LeafClaim)
require.Equal(t, middle, step.StateClaim)
require.True(t, step.IsAttack) require.True(t, step.IsAttack)
_, err = solver.AttemptStep(middle, g) _, err = solver.AttemptStep(middle)
require.Error(t, err) require.Error(t, err)
} }
...@@ -14,7 +14,6 @@ var ( ...@@ -14,7 +14,6 @@ var (
// StepCallData encapsulates the data needed to perform a step. // StepCallData encapsulates the data needed to perform a step.
type StepCallData struct { type StepCallData struct {
StateIndex uint64
ClaimIndex uint64 ClaimIndex uint64
IsAttack bool IsAttack bool
StateData []byte StateData []byte
......
...@@ -38,11 +38,11 @@ func LightApplicationScoreParams(cfg *rollup.Config) ApplicationScoreParams { ...@@ -38,11 +38,11 @@ func LightApplicationScoreParams(cfg *rollup.Config) ApplicationScoreParams {
ValidResponseWeight: 0.5, ValidResponseWeight: 0.5,
ValidResponseDecay: ScoreDecay(tenEpochs, slot), ValidResponseDecay: ScoreDecay(tenEpochs, slot),
// Takes 20 error responses to reach the default ban threshold of -100 // Takes 10 error responses to reach the default gossip threshold of -10
// But at most we track 10. These errors include not supporting p2p sync // But at most we track 9. These errors include not supporting p2p sync
// so we don't (yet) want to ban a peer based on this measure alone. // so we don't (yet) want to ignore gossip from a peer based on this measure alone.
ErrorResponseCap: 10, ErrorResponseCap: 9,
ErrorResponseWeight: -5, ErrorResponseWeight: -1,
ErrorResponseDecay: ScoreDecay(tenEpochs, slot), ErrorResponseDecay: ScoreDecay(tenEpochs, slot),
// Takes 5 rejected payloads to reach the default ban threshold of -100 // Takes 5 rejected payloads to reach the default ban threshold of -100
......
...@@ -15,7 +15,7 @@ type Metrics interface { ...@@ -15,7 +15,7 @@ type Metrics interface {
RecordL1Ref(name string, ref eth.L1BlockRef) RecordL1Ref(name string, ref eth.L1BlockRef)
RecordL2Ref(name string, ref eth.L2BlockRef) RecordL2Ref(name string, ref eth.L2BlockRef)
RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID) RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID)
RecordChannelInputBytes(inputCompresedBytes int) RecordChannelInputBytes(inputCompressedBytes int)
} }
type L1Fetcher interface { type L1Fetcher interface {
......
...@@ -21,7 +21,7 @@ type Metrics interface { ...@@ -21,7 +21,7 @@ type Metrics interface {
RecordL1Ref(name string, ref eth.L1BlockRef) RecordL1Ref(name string, ref eth.L1BlockRef)
RecordL2Ref(name string, ref eth.L2BlockRef) RecordL2Ref(name string, ref eth.L2BlockRef)
RecordChannelInputBytes(inputCompresedBytes int) RecordChannelInputBytes(inputCompressedBytes int)
RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID) RecordUnsafePayloadsBuffer(length uint64, memSize uint64, next eth.BlockID)
......
...@@ -11,7 +11,7 @@ type TestDerivationMetrics struct { ...@@ -11,7 +11,7 @@ type TestDerivationMetrics struct {
FnRecordL1Ref func(name string, ref eth.L1BlockRef) FnRecordL1Ref func(name string, ref eth.L1BlockRef)
FnRecordL2Ref func(name string, ref eth.L2BlockRef) FnRecordL2Ref func(name string, ref eth.L2BlockRef)
FnRecordUnsafePayloads func(length uint64, memSize uint64, next eth.BlockID) FnRecordUnsafePayloads func(length uint64, memSize uint64, next eth.BlockID)
FnRecordChannelInputBytes func(inputCompresedBytes int) FnRecordChannelInputBytes func(inputCompressedBytes int)
} }
func (t *TestDerivationMetrics) RecordL1ReorgDepth(d uint64) { func (t *TestDerivationMetrics) RecordL1ReorgDepth(d uint64) {
...@@ -38,9 +38,9 @@ func (t *TestDerivationMetrics) RecordUnsafePayloadsBuffer(length uint64, memSiz ...@@ -38,9 +38,9 @@ func (t *TestDerivationMetrics) RecordUnsafePayloadsBuffer(length uint64, memSiz
} }
} }
func (t *TestDerivationMetrics) RecordChannelInputBytes(inputCompresedBytes int) { func (t *TestDerivationMetrics) RecordChannelInputBytes(inputCompressedBytes int) {
if t.FnRecordChannelInputBytes != nil { if t.FnRecordChannelInputBytes != nil {
t.FnRecordChannelInputBytes(inputCompresedBytes) t.FnRecordChannelInputBytes(inputCompressedBytes)
} }
} }
......
...@@ -7,7 +7,7 @@ import { ...@@ -7,7 +7,7 @@ import {
waitForProvider, waitForProvider,
} from '@eth-optimism/common-ts' } from '@eth-optimism/common-ts'
import { getChainId, compareAddrs } from '@eth-optimism/core-utils' import { getChainId, compareAddrs } from '@eth-optimism/core-utils'
import { Provider } from '@ethersproject/abstract-provider' import { Provider, TransactionResponse } from '@ethersproject/abstract-provider'
import mainnetConfig from '@eth-optimism/contracts-bedrock/deploy-config/mainnet.json' import mainnetConfig from '@eth-optimism/contracts-bedrock/deploy-config/mainnet.json'
import goerliConfig from '@eth-optimism/contracts-bedrock/deploy-config/goerli.json' import goerliConfig from '@eth-optimism/contracts-bedrock/deploy-config/goerli.json'
import l2OutputOracleArtifactsMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/L2OutputOracleProxy.json' import l2OutputOracleArtifactsMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/L2OutputOracleProxy.json'
...@@ -101,7 +101,7 @@ export class WalletMonService extends BaseServiceV2< ...@@ -101,7 +101,7 @@ export class WalletMonService extends BaseServiceV2<
unexpectedCalls: { unexpectedCalls: {
type: Counter, type: Counter,
desc: 'Number of unexpected wallets', desc: 'Number of unexpected wallets',
labels: ['wallet', 'target', 'nickname'], labels: ['wallet', 'target', 'nickname', 'transactionHash'],
}, },
unexpectedRpcErrors: { unexpectedRpcErrors: {
type: Counter, type: Counter,
...@@ -150,7 +150,7 @@ export class WalletMonService extends BaseServiceV2< ...@@ -150,7 +150,7 @@ export class WalletMonService extends BaseServiceV2<
number: block.number, number: block.number,
}) })
const transactions = [] const transactions: TransactionResponse[] = []
for (const txHash of block.transactions) { for (const txHash of block.transactions) {
const t = await this.options.rpc.getTransaction(txHash) const t = await this.options.rpc.getTransaction(txHash)
transactions.push(t) transactions.push(t)
...@@ -175,11 +175,13 @@ export class WalletMonService extends BaseServiceV2< ...@@ -175,11 +175,13 @@ export class WalletMonService extends BaseServiceV2<
nickname: account.label, nickname: account.label,
wallet: account.address, wallet: account.address,
target: transaction.to, target: transaction.to,
transactionHash: transaction.hash,
}) })
this.logger.error('Unexpected call detected', { this.logger.error('Unexpected call detected', {
nickname: account.label, nickname: account.label,
address: account.address, address: account.address,
target: transaction.to, target: transaction.to,
transactionHash: transaction.hash,
}) })
} }
} }
......
...@@ -379,7 +379,7 @@ contract MIPS { ...@@ -379,7 +379,7 @@ contract MIPS {
} }
// will revert if any required input state is missing // will revert if any required input state is missing
function Step(bytes calldata stateData, bytes calldata proof) public returns (bytes32) { function step(bytes calldata stateData, bytes calldata proof) public returns (bytes32) {
State memory state; State memory state;
// packed data is ~6 times smaller // packed data is ~6 times smaller
assembly { assembly {
......
...@@ -48,7 +48,6 @@ ...@@ -48,7 +48,6 @@
"@typescript-eslint/parser": "^5.60.1", "@typescript-eslint/parser": "^5.60.1",
"ds-test": "github:dapphub/ds-test#c9ce3f25bde29fc5eb9901842bf02850dfd2d084", "ds-test": "github:dapphub/ds-test#c9ce3f25bde29fc5eb9901842bf02850dfd2d084",
"forge-std": "github:foundry-rs/forge-std#e8a047e3f40f13fa37af6fe14e6e06283d9a060e", "forge-std": "github:foundry-rs/forge-std#e8a047e3f40f13fa37af6fe14e6e06283d9a060e",
"glob": "^7.1.6",
"solhint": "^3.4.1", "solhint": "^3.4.1",
"solhint-plugin-prettier": "^0.0.5", "solhint-plugin-prettier": "^0.0.5",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
......
import fs from 'fs' import fs from 'fs'
import path from 'path'
import { glob } from 'glob' import { execSync } from 'child_process'
/** /**
* Series of function name checks. * Series of function name checks.
...@@ -59,27 +59,67 @@ const checks: Array<{ ...@@ -59,27 +59,67 @@ const checks: Array<{
* Script for checking that all test functions are named correctly. * Script for checking that all test functions are named correctly.
*/ */
const main = async () => { const main = async () => {
const result = execSync('forge config --json')
const config = JSON.parse(result.toString())
const out = config.out || 'out'
const paths = []
const readFilesRecursively = (dir: string) => {
const files = fs.readdirSync(dir)
for (const file of files) {
const filePath = path.join(dir, file)
const fileStat = fs.statSync(filePath)
if (fileStat.isDirectory()) {
readFilesRecursively(filePath)
} else {
paths.push(filePath)
}
}
}
readFilesRecursively(out)
console.log('Success:')
const errors: string[] = [] const errors: string[] = []
const files = glob.sync('./forge-artifacts/**/*.t.sol/*Test*.json')
for (const file of files) { for (const filepath of paths) {
const artifact = JSON.parse(fs.readFileSync(file, 'utf8')) const artifact = JSON.parse(fs.readFileSync(filepath, 'utf8'))
let isTest = false
for (const element of artifact.abi) { for (const element of artifact.abi) {
// Skip non-functions and functions that don't start with "test". if (element.name === 'IS_TEST') {
if (element.type !== 'function' || !element.name.startsWith('test')) { isTest = true
continue break
} }
}
if (isTest) {
let success = true
for (const element of artifact.abi) {
// Skip non-functions and functions that don't start with "test".
if (element.type !== 'function' || !element.name.startsWith('test')) {
continue
}
// Check the rest. // Check the rest.
for (const { check, error } of checks) { for (const { check, error } of checks) {
if (!check(element.name.split('_'))) { if (!check(element.name.split('_'))) {
errors.push(`in ${file} function ${element.name}: ${error}`) errors.push(`${filepath} function ${element.name}: ${error}`)
success = false
}
} }
} }
if (success) {
console.log(` - ${path.parse(filepath).name}`)
}
} }
} }
if (errors.length > 0) { if (errors.length > 0) {
console.error(...errors) console.error(errors.join('\n'))
process.exit(1) process.exit(1)
} }
} }
......
...@@ -1563,9 +1563,17 @@ export class CrossChainMessenger { ...@@ -1563,9 +1563,17 @@ export class CrossChainMessenger {
opts?: { opts?: {
signer?: Signer signer?: Signer
overrides?: Overrides overrides?: Overrides
} },
/**
* The index of the withdrawal if multiple are made with multicall
*/
messageIndex: number = 0
): Promise<TransactionResponse> { ): Promise<TransactionResponse> {
const tx = await this.populateTransaction.proveMessage(message, opts) const tx = await this.populateTransaction.proveMessage(
message,
opts,
messageIndex
)
return (opts?.signer || this.l1Signer).sendTransaction(tx) return (opts?.signer || this.l1Signer).sendTransaction(tx)
} }
...@@ -1584,10 +1592,18 @@ export class CrossChainMessenger { ...@@ -1584,10 +1592,18 @@ export class CrossChainMessenger {
opts?: { opts?: {
signer?: Signer signer?: Signer
overrides?: PayableOverrides overrides?: PayableOverrides
} },
/**
* The index of the withdrawal if multiple are made with multicall
*/
messageIndex = 0
): Promise<TransactionResponse> { ): Promise<TransactionResponse> {
return (opts?.signer || this.l1Signer).sendTransaction( return (opts?.signer || this.l1Signer).sendTransaction(
await this.populateTransaction.finalizeMessage(message, opts) await this.populateTransaction.finalizeMessage(
message,
opts,
messageIndex
)
) )
} }
...@@ -1810,7 +1826,6 @@ export class CrossChainMessenger { ...@@ -1810,7 +1826,6 @@ export class CrossChainMessenger {
opts?: { opts?: {
overrides?: Overrides overrides?: Overrides
}, },
/** /**
* The index of the withdrawal if multiple are made with multicall * The index of the withdrawal if multiple are made with multicall
*/ */
...@@ -1822,13 +1837,17 @@ export class CrossChainMessenger { ...@@ -1822,13 +1837,17 @@ export class CrossChainMessenger {
} }
if (this.bedrock) { if (this.bedrock) {
return this.populateTransaction.finalizeMessage(resolved, { return this.populateTransaction.finalizeMessage(
...(opts || {}), resolved,
overrides: { {
...opts?.overrides, ...(opts || {}),
gasLimit: messageGasLimit, overrides: {
...opts?.overrides,
gasLimit: messageGasLimit,
},
}, },
}) messageIndex
)
} else { } else {
const legacyL1XDM = new ethers.Contract( const legacyL1XDM = new ethers.Contract(
this.contracts.l1.L1CrossDomainMessenger.address, this.contracts.l1.L1CrossDomainMessenger.address,
...@@ -1861,7 +1880,6 @@ export class CrossChainMessenger { ...@@ -1861,7 +1880,6 @@ export class CrossChainMessenger {
opts?: { opts?: {
overrides?: PayableOverrides overrides?: PayableOverrides
}, },
/** /**
* The index of the withdrawal if multiple are made with multicall * The index of the withdrawal if multiple are made with multicall
*/ */
...@@ -1921,7 +1939,6 @@ export class CrossChainMessenger { ...@@ -1921,7 +1939,6 @@ export class CrossChainMessenger {
opts?: { opts?: {
overrides?: PayableOverrides overrides?: PayableOverrides
}, },
/** /**
* The index of the withdrawal if multiple are made with multicall * The index of the withdrawal if multiple are made with multicall
*/ */
...@@ -2229,10 +2246,18 @@ export class CrossChainMessenger { ...@@ -2229,10 +2246,18 @@ export class CrossChainMessenger {
message: MessageLike, message: MessageLike,
opts?: { opts?: {
overrides?: CallOverrides overrides?: CallOverrides
} },
/**
* The index of the withdrawal if multiple are made with multicall
*/
messageIndex = 0
): Promise<BigNumber> => { ): Promise<BigNumber> => {
return this.l1Provider.estimateGas( return this.l1Provider.estimateGas(
await this.populateTransaction.finalizeMessage(message, opts) await this.populateTransaction.finalizeMessage(
message,
opts,
messageIndex
)
) )
}, },
......
...@@ -123,6 +123,9 @@ importers: ...@@ -123,6 +123,9 @@ importers:
prettier-plugin-solidity: prettier-plugin-solidity:
specifier: ^1.0.0-beta.13 specifier: ^1.0.0-beta.13
version: 1.0.0-beta.18(prettier@2.8.1) version: 1.0.0-beta.18(prettier@2.8.1)
rimraf:
specifier: ^5.0.1
version: 5.0.1
ts-mocha: ts-mocha:
specifier: ^10.0.0 specifier: ^10.0.0
version: 10.0.0(mocha@8.4.0) version: 10.0.0(mocha@8.4.0)
...@@ -277,9 +280,6 @@ importers: ...@@ -277,9 +280,6 @@ importers:
forge-std: forge-std:
specifier: github:foundry-rs/forge-std#e8a047e3f40f13fa37af6fe14e6e06283d9a060e specifier: github:foundry-rs/forge-std#e8a047e3f40f13fa37af6fe14e6e06283d9a060e
version: github.com/foundry-rs/forge-std/e8a047e3f40f13fa37af6fe14e6e06283d9a060e version: github.com/foundry-rs/forge-std/e8a047e3f40f13fa37af6fe14e6e06283d9a060e
glob:
specifier: ^7.1.6
version: 7.1.6
solhint: solhint:
specifier: ^3.4.1 specifier: ^3.4.1
version: 3.4.1 version: 3.4.1
...@@ -9836,7 +9836,7 @@ packages: ...@@ -9836,7 +9836,7 @@ packages:
fs.realpath: 1.0.0 fs.realpath: 1.0.0
minimatch: 8.0.4 minimatch: 8.0.4
minipass: 4.2.8 minipass: 4.2.8
path-scurry: 1.9.2 path-scurry: 1.10.0
dev: true dev: true
/global-modules@2.0.0: /global-modules@2.0.0:
...@@ -12152,11 +12152,6 @@ packages: ...@@ -12152,11 +12152,6 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true dev: true
/lru-cache@9.1.2:
resolution: {integrity: sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==}
engines: {node: 14 || >=16.14}
dev: true
/lru_map@0.3.3: /lru_map@0.3.3:
resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==}
...@@ -14534,14 +14529,6 @@ packages: ...@@ -14534,14 +14529,6 @@ packages:
minipass: 6.0.2 minipass: 6.0.2
dev: true dev: true
/path-scurry@1.9.2:
resolution: {integrity: sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==}
engines: {node: '>=16 || 14 >=14.17'}
dependencies:
lru-cache: 9.1.2
minipass: 6.0.2
dev: true
/path-to-regexp@0.1.7: /path-to-regexp@0.1.7:
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
...@@ -15810,6 +15797,14 @@ packages: ...@@ -15810,6 +15797,14 @@ packages:
glob: 9.3.5 glob: 9.3.5
dev: true dev: true
/rimraf@5.0.1:
resolution: {integrity: sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==}
engines: {node: '>=14'}
hasBin: true
dependencies:
glob: 10.3.1
dev: true
/ripemd160@2.0.2: /ripemd160@2.0.2:
resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==}
dependencies: dependencies:
......
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