Commit bcdf0d12 authored by Adrian Sutton's avatar Adrian Sutton

Merge branch 'develop' into aj/rearrange-fault-proof-e2e

parents 48f0628e 363d188f
......@@ -346,7 +346,7 @@ jobs:
contracts-bedrock-tests:
docker:
- image: <<pipeline.parameters.ci_builder_image>>
resource_class: medium
resource_class: xlarge
steps:
- checkout
- run:
......@@ -378,7 +378,7 @@ jobs:
contracts-bedrock-checks:
docker:
- image: <<pipeline.parameters.ci_builder_image>>
resource_class: medium
resource_class: xlarge
steps:
- checkout
- run:
......@@ -432,6 +432,16 @@ jobs:
pnpm autogen:invariant-docs
git diff --exit-code ./invariant-docs/*.md || echo "export INVARIANT_DOCS_STATUS=1" >> "$BASH_ENV"
working_directory: packages/contracts-bedrock
- run:
name: abi snapshot
command: |
pnpm abi-snapshot
if [ "$(git diff --exit-code snapshots)" ]; then
[ -z "$(git ls-files --others --exclude-standard snapshots)" ] || echo "export ABI_SNAPSHOT_STATUS=1" >> "$BASH_ENV"
else
echo "export ABI_SNAPSHOT_STATUS=1" >> "$BASH_ENV"
fi
working_directory: packages/contracts-bedrock
- run:
name: check statuses
command: |
......@@ -459,6 +469,10 @@ jobs:
echo "Deploy config check failed, see job output for details."
FAILED=1
fi
if [[ "$ABI_SNAPSHOT_STATUS" -ne 0 ]]; then
echo "ABI snapshot check failed, see job output for details."
FAILED=1
fi
if [[ "$FAILED" -ne 0 ]]; then
exit 1
fi
......
......@@ -61,7 +61,7 @@ type BridgeMessagesDB interface {
StoreL2BridgeMessages([]L2BridgeMessage) error
MarkRelayedL2BridgeMessage(common.Hash, uuid.UUID) error
StoreL2BridgeMessageV1MessageHash(common.Hash, common.Hash) error
StoreL2BridgeMessageV1MessageHashes([]L2BridgeMessageVersionedMessageHash) error
}
/**
......@@ -141,15 +141,11 @@ func (db bridgeMessagesDB) StoreL2BridgeMessages(messages []L2BridgeMessage) err
return result.Error
}
func (db bridgeMessagesDB) StoreL2BridgeMessageV1MessageHash(msgHash, v1MsgHash common.Hash) error {
if msgHash == v1MsgHash {
return fmt.Errorf("message hash is equal to the v1 message: %s", msgHash)
}
func (db bridgeMessagesDB) StoreL2BridgeMessageV1MessageHashes(versionedHashes []L2BridgeMessageVersionedMessageHash) error {
deduped := db.gorm.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "message_hash"}}, DoNothing: true})
result := deduped.Create(&L2BridgeMessageVersionedMessageHash{MessageHash: msgHash, V1MessageHash: v1MsgHash})
if result.Error == nil && int(result.RowsAffected) < 1 {
db.log.Warn("ignored L2 bridge v1 message hash duplicates")
result := deduped.Create(&versionedHashes)
if result.Error == nil && int(result.RowsAffected) < len(versionedHashes) {
db.log.Warn("ignored L2 bridge v1 message hash duplicates", "duplicates", len(versionedHashes)-int(result.RowsAffected))
}
return result.Error
......
......@@ -13,8 +13,10 @@ import (
"github.com/ethereum-optimism/optimism/indexer/client"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/prometheus/client_golang/prometheus"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
......@@ -30,7 +32,8 @@ import (
*/
type E2ETestSuite struct {
t *testing.T
t *testing.T
MetricsRegistry *prometheus.Registry
// API
Client *client.Client
......@@ -152,14 +155,15 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
require.NoError(t, err, "must open indexer API client")
return E2ETestSuite{
t: t,
Client: client,
DB: ix.DB,
Indexer: ix,
OpCfg: &opCfg,
OpSys: opSys,
L1Client: opSys.Clients["l1"],
L2Client: opSys.Clients["sequencer"],
t: t,
MetricsRegistry: metrics.NewRegistry(),
Client: client,
DB: ix.DB,
Indexer: ix,
OpCfg: &opCfg,
OpSys: opSys,
L1Client: opSys.Clients["l1"],
L2Client: opSys.Clients["sequencer"],
}
}
......
......@@ -312,7 +312,7 @@ func (b *BridgeProcessor) processFinalizedL1Events() error {
legacyBridgeLog := l1BridgeLog.New("mode", "legacy", "from_block_number", legacyFromL1Height, "to_block_number", legacyToL1Height)
legacyBridgeLog.Info("scanning for finalized bridge events")
if err := bridge.LegacyL1ProcessFinalizedBridgeEvents(legacyBridgeLog, tx, b.metrics, b.l1Etl.EthClient, b.chainConfig.L1Contracts, legacyFromL1Height, legacyToL1Height); err != nil {
if err := bridge.LegacyL1ProcessFinalizedBridgeEvents(legacyBridgeLog, tx, b.metrics, b.chainConfig.L1Contracts, legacyFromL1Height, legacyToL1Height); err != nil {
return err
} else if legacyToL1Height.Cmp(toL1Height) == 0 {
return nil // a-ok! Entire range was legacy blocks
......
......@@ -7,6 +7,7 @@ import (
"github.com/ethereum-optimism/optimism/indexer/bigint"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/processors/bridge/ovm1"
"github.com/ethereum-optimism/optimism/indexer/processors/contracts"
"github.com/ethereum/go-ethereum/common"
......@@ -155,21 +156,30 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
log.Info("detected proven withdrawals", "size", len(provenWithdrawals))
}
skippedOVM1ProvenWithdrawals := 0
for i := range provenWithdrawals {
proven := provenWithdrawals[i]
withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(proven.WithdrawalHash)
provenWithdrawal := provenWithdrawals[i]
withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(provenWithdrawal.WithdrawalHash)
if err != nil {
return err
} else if withdrawal == nil {
return fmt.Errorf("missing indexed withdrawal! tx_hash = %s", proven.Event.TransactionHash)
if _, ok := ovm1.PortalWithdrawalTransactions[provenWithdrawal.WithdrawalHash]; ok {
skippedOVM1ProvenWithdrawals++
continue
}
return fmt.Errorf("missing indexed withdrawal! tx_hash = %s", provenWithdrawal.Event.TransactionHash)
}
if err := db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(proven.WithdrawalHash, provenWithdrawals[i].Event.GUID); err != nil {
return fmt.Errorf("failed to mark withdrawal as proven. tx_hash = %s: %w", proven.Event.TransactionHash, err)
if err := db.BridgeTransactions.MarkL2TransactionWithdrawalProvenEvent(provenWithdrawal.WithdrawalHash, provenWithdrawals[i].Event.GUID); err != nil {
return fmt.Errorf("failed to mark withdrawal as proven. tx_hash = %s: %w", provenWithdrawal.Event.TransactionHash, err)
}
}
if len(provenWithdrawals) > 0 {
metrics.RecordL1ProvenWithdrawals(len(provenWithdrawals))
if skippedOVM1ProvenWithdrawals > 0 {
metrics.RecordL1SkippedOVM1ProvenWithdrawals(skippedOVM1ProvenWithdrawals)
log.Info("skipped OVM 1.0 proven withdrawals", "size", skippedOVM1ProvenWithdrawals)
}
}
// (2) OptimismPortal (finalized withdrawals)
......@@ -181,12 +191,17 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
log.Info("detected finalized withdrawals", "size", len(finalizedWithdrawals))
}
skippedOVM1FinalizedWithdrawals := 0
for i := range finalizedWithdrawals {
finalizedWithdrawal := finalizedWithdrawals[i]
withdrawal, err := db.BridgeTransactions.L2TransactionWithdrawal(finalizedWithdrawal.WithdrawalHash)
if err != nil {
return err
} else if withdrawal == nil {
if _, ok := ovm1.PortalWithdrawalTransactions[finalizedWithdrawal.WithdrawalHash]; ok {
skippedOVM1FinalizedWithdrawals++
continue
}
return fmt.Errorf("missing indexed withdrawal on finalization! tx_hash = %s", finalizedWithdrawal.Event.TransactionHash.String())
}
......@@ -196,6 +211,10 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
}
if len(finalizedWithdrawals) > 0 {
metrics.RecordL1FinalizedWithdrawals(len(finalizedWithdrawals))
if skippedOVM1ProvenWithdrawals > 0 {
metrics.RecordL1SkippedOVM1FinalizedWithdrawals(skippedOVM1FinalizedWithdrawals)
log.Info("skipped OVM 1.0 finalized withdrawals", "size", skippedOVM1FinalizedWithdrawals)
}
}
// (3) L1CrossDomainMessenger
......@@ -207,21 +226,30 @@ func L1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1M
log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages))
}
skippedOVM1Messages := 0
for i := range crossDomainRelayedMessages {
relayed := crossDomainRelayedMessages[i]
message, err := db.BridgeMessages.L2BridgeMessage(relayed.MessageHash)
relayedMessage := crossDomainRelayedMessages[i]
message, err := db.BridgeMessages.L2BridgeMessage(relayedMessage.MessageHash)
if err != nil {
return err
} else if message == nil {
return fmt.Errorf("missing indexed L2CrossDomainMessager message! tx_hash = %s", relayed.Event.TransactionHash.String())
if _, ok := ovm1.L1RelayedMessages[relayedMessage.MessageHash]; ok {
skippedOVM1Messages++
continue
}
return fmt.Errorf("missing indexed L2CrossDomainMessager message! tx_hash = %s", relayedMessage.Event.TransactionHash.String())
}
if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayed.MessageHash, relayed.Event.GUID); err != nil {
return fmt.Errorf("failed to relay cross domain message. tx_hash = %s: %w", relayed.Event.TransactionHash, err)
if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
return fmt.Errorf("failed to relay cross domain message. tx_hash = %s: %w", relayedMessage.Event.TransactionHash, err)
}
}
if len(crossDomainRelayedMessages) > 0 {
metrics.RecordL1CrossDomainRelayedMessages(len(crossDomainRelayedMessages))
if skippedOVM1Messages > 0 { // Logged as a warning just for visibility
metrics.RecordL1SkippedOVM1CrossDomainRelayedMessages(skippedOVM1Messages)
log.Info("skipped OVM 1.0 relayed L2CrossDomainMessenger withdrawals", "size", skippedOVM1Messages)
}
}
// (4) L1StandardBridge
......
......@@ -10,8 +10,9 @@ import (
"github.com/ethereum-optimism/optimism/indexer/bigint"
"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum-optimism/optimism/indexer/node"
"github.com/ethereum-optimism/optimism/indexer/processors/bridge/ovm1"
"github.com/ethereum-optimism/optimism/indexer/processors/contracts"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
)
......@@ -165,6 +166,7 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
withdrawnWEI := bigint.Zero
sentMessages := make(map[logKey]sentMessageEvent, len(crossDomainSentMessages))
bridgeMessages := make([]database.L2BridgeMessage, len(crossDomainSentMessages))
versionedMessageHashes := make([]database.L2BridgeMessageVersionedMessageHash, len(crossDomainSentMessages))
transactionWithdrawals := make([]database.L2TransactionWithdrawal, len(crossDomainSentMessages))
for i := range crossDomainSentMessages {
sentMessage := crossDomainSentMessages[i]
......@@ -172,15 +174,14 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
// Since these message can be relayed in bedrock, we utilize the migrated withdrawal hash
// and also store the v1 version of the message hash such that the bedrock l1 finalization
// processor works as expected
// processor works as expected. There will be some entries relayed pre-bedrock that will not
// require the entry but we'll store it anyways as a large % of these withdrawals are not relayed
// pre-bedrock.
v1MessageHash, err := legacyBridgeMessageV1MessageHash(&sentMessage.BridgeMessage)
if err != nil {
return fmt.Errorf("failed to compute versioned message hash: %w", err)
}
if err := db.BridgeMessages.StoreL2BridgeMessageV1MessageHash(sentMessage.BridgeMessage.MessageHash, v1MessageHash); err != nil {
return err
}
withdrawalHash, err := legacyBridgeMessageWithdrawalHash(preset, &sentMessage.BridgeMessage)
if err != nil {
......@@ -202,10 +203,8 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
}
sentMessages[logKey{sentMessage.Event.BlockHash, sentMessage.Event.LogIndex}] = sentMessageEvent{&sentMessage, withdrawalHash}
bridgeMessages[i] = database.L2BridgeMessage{
TransactionWithdrawalHash: sentMessage.BridgeMessage.MessageHash,
BridgeMessage: sentMessage.BridgeMessage,
}
bridgeMessages[i] = database.L2BridgeMessage{TransactionWithdrawalHash: withdrawalHash, BridgeMessage: sentMessage.BridgeMessage}
versionedMessageHashes[i] = database.L2BridgeMessageVersionedMessageHash{MessageHash: sentMessage.BridgeMessage.MessageHash, V1MessageHash: v1MessageHash}
}
if len(bridgeMessages) > 0 {
if err := db.BridgeTransactions.StoreL2TransactionWithdrawals(transactionWithdrawals); err != nil {
......@@ -214,6 +213,9 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
if err := db.BridgeMessages.StoreL2BridgeMessages(bridgeMessages); err != nil {
return err
}
if err := db.BridgeMessages.StoreL2BridgeMessageV1MessageHashes(versionedMessageHashes); err != nil {
return err
}
withdrawnETH, _ := bigint.WeiToETH(withdrawnWEI).Float64()
metrics.RecordL2TransactionWithdrawals(len(transactionWithdrawals), withdrawnETH)
......@@ -270,7 +272,7 @@ func LegacyL2ProcessInitiatedBridgeEvents(log log.Logger, db *database.DB, metri
// according to the pre-bedrock protocol. This follows:
// 1. L1CrossDomainMessenger
// 2. L1StandardBridge
func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1Metricer, l1Client node.EthClient, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metrics L1Metricer, l1Contracts config.L1Contracts, fromHeight, toHeight *big.Int) error {
// (1) L1CrossDomainMessenger -> This is the root-most contract from which bridge events are finalized since withdrawals must be initiated from the
// L2CrossDomainMessenger. Since there's no two-step withdrawal process, we mark the transaction as proven/finalized in the same step
crossDomainRelayedMessages, err := contracts.CrossDomainMessengerRelayedMessageEvents("l1", l1Contracts.L1CrossDomainMessengerProxy, db, fromHeight, toHeight)
......@@ -281,38 +283,19 @@ func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri
log.Info("detected relayed messages", "size", len(crossDomainRelayedMessages))
}
skippedPreRegenesisMessages := 0
skippedOVM1Messages := 0
for i := range crossDomainRelayedMessages {
relayedMessage := crossDomainRelayedMessages[i]
message, err := db.BridgeMessages.L2BridgeMessage(relayedMessage.MessageHash)
if err != nil {
return err
} else if message == nil {
// Before surfacing an error about a missing withdrawal, we need to handle an edge case
// for OP-Mainnet pre-regensis withdrawals that no longer exist on L2.
tx, err := l1Client.TxByHash(relayedMessage.Event.TransactionHash)
if err != nil {
return fmt.Errorf("unable to query legacy relayed. tx_hash = %s: %w", relayedMessage.Event.TransactionHash, err)
} else if tx == nil {
return fmt.Errorf("missing tx for relayed message! tx_hash = %s", relayedMessage.Event.TransactionHash)
}
relayMessageData := tx.Data()[4:]
inputs, err := contracts.CrossDomainMessengerLegacyRelayMessageEncoding.Inputs.Unpack(relayMessageData)
if err != nil || inputs == nil {
return fmt.Errorf("unable to extract XDomainCallData from relayMessage transaction. tx_hash = %s: %w", relayedMessage.Event.TransactionHash, err)
}
// NOTE: Since OP-Mainnet is the only network to go through a regensis we can simply harcode the
// the starting message nonce at genesis (100k). Any relayed withdrawal on L1 with a lesser nonce
// is a clear indicator of a pre-regenesis withdrawal.
if inputs[3].(*big.Int).Int64() < 100_000 {
// skip pre-regenesis withdrawals
skippedPreRegenesisMessages++
if _, ok := ovm1.L1RelayedMessages[relayedMessage.MessageHash]; ok {
skippedOVM1Messages++
continue
} else {
return fmt.Errorf("missing indexed L2CrossDomainMessenger message! tx_hash = %s", relayedMessage.Event.TransactionHash)
}
return fmt.Errorf("missing indexed L2CrossDomainMessenger message! tx_hash %s", relayedMessage.Event.TransactionHash.String())
}
if err := db.BridgeMessages.MarkRelayedL2BridgeMessage(relayedMessage.MessageHash, relayedMessage.Event.GUID); err != nil {
......@@ -328,13 +311,11 @@ func LegacyL1ProcessFinalizedBridgeEvents(log log.Logger, db *database.DB, metri
}
}
if len(crossDomainRelayedMessages) > 0 {
metrics.RecordL1ProvenWithdrawals(len(crossDomainRelayedMessages))
metrics.RecordL1FinalizedWithdrawals(len(crossDomainRelayedMessages))
metrics.RecordL1CrossDomainRelayedMessages(len(crossDomainRelayedMessages))
}
if skippedPreRegenesisMessages > 0 {
// Logged as a warning just for visibility
log.Warn("skipped pre-regensis relayed L2CrossDomainMessenger withdrawals", "size", skippedPreRegenesisMessages)
if skippedOVM1Messages > 0 { // Logged as a warning just for visibility
metrics.RecordL1SkippedOVM1CrossDomainRelayedMessages(skippedOVM1Messages)
log.Info("skipped OVM 1.0 relayed L2CrossDomainMessenger withdrawals", "size", skippedOVM1Messages)
}
}
// (2) L1StandardBridge
......
......@@ -25,6 +25,10 @@ type L1Metricer interface {
RecordL1CrossDomainSentMessages(size int)
RecordL1CrossDomainRelayedMessages(size int)
RecordL1SkippedOVM1ProvenWithdrawals(size int)
RecordL1SkippedOVM1FinalizedWithdrawals(size int)
RecordL1SkippedOVM1CrossDomainRelayedMessages(size int)
RecordL1InitiatedBridgeTransfers(token common.Address, size int)
RecordL1FinalizedBridgeTransfers(token common.Address, size int)
}
......@@ -56,15 +60,19 @@ type bridgeMetrics struct {
intervalFailures *prometheus.CounterVec
txDeposits prometheus.Counter
txMintedETH prometheus.Counter
txWithdrawals prometheus.Counter
txWithdrawnETH prometheus.Counter
provenWithdrawals prometheus.Counter
finalizedWithdrawals prometheus.Counter
txMintedETH prometheus.Counter
txWithdrawnETH prometheus.Counter
sentMessages *prometheus.CounterVec
relayedMessages *prometheus.CounterVec
skippedOVM1Withdrawals *prometheus.CounterVec
skippedOVM1RelayedMessages prometheus.Counter
initiatedBridgeTransfers *prometheus.CounterVec
finalizedBridgeTransfers *prometheus.CounterVec
}
......@@ -114,7 +122,7 @@ func NewMetrics(registry *prometheus.Registry) Metricer {
txWithdrawals: factory.NewCounter(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Name: "tx_withdrawals",
Help: "number of processed transactions withdrawn from l2",
Help: "number of initiated transaction withdrawals from l2",
}),
txWithdrawnETH: factory.NewCounter(prometheus.CounterOpts{
Namespace: MetricsNamespace,
......@@ -145,6 +153,18 @@ func NewMetrics(registry *prometheus.Registry) Metricer {
}, []string{
"chain",
}),
skippedOVM1Withdrawals: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Name: "skipped_ovm1_withdrawals",
Help: "number of skipped ovm 1.0 withdrawals on l1 (proven|finalized)",
}, []string{
"stage",
}),
skippedOVM1RelayedMessages: factory.NewCounter(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Name: "skipped_ovm1_relayed_messages",
Help: "number of skipped ovm 1.0 relayed messages on l1",
}),
initiatedBridgeTransfers: factory.NewCounterVec(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Name: "initiated_token_transfers",
......@@ -206,6 +226,18 @@ func (m *bridgeMetrics) RecordL1CrossDomainRelayedMessages(size int) {
m.relayedMessages.WithLabelValues("l1").Add(float64(size))
}
func (m *bridgeMetrics) RecordL1SkippedOVM1ProvenWithdrawals(size int) {
m.skippedOVM1Withdrawals.WithLabelValues("stage", "proven").Add(float64(size))
}
func (m *bridgeMetrics) RecordL1SkippedOVM1FinalizedWithdrawals(size int) {
m.skippedOVM1Withdrawals.WithLabelValues("stage", "finalized").Add(float64(size))
}
func (m *bridgeMetrics) RecordL1SkippedOVM1CrossDomainRelayedMessages(size int) {
m.skippedOVM1RelayedMessages.Add(float64(size))
}
func (m *bridgeMetrics) RecordL1InitiatedBridgeTransfers(tokenAddr common.Address, size int) {
m.initiatedBridgeTransfers.WithLabelValues("l1", tokenAddr.String()).Add(float64(size))
}
......
This diff is collapsed.
This diff is collapsed.
......@@ -4,7 +4,6 @@ import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/indexer/bigint"
......@@ -13,31 +12,6 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
)
var (
// Standard ABI types copied from golang ABI tests
addressType, _ = abi.NewType("address", "", nil)
bytesType, _ = abi.NewType("bytes", "", nil)
uint256Type, _ = abi.NewType("uint256", "", nil)
CrossDomainMessengerLegacyRelayMessageEncoding = abi.NewMethod(
"relayMessage",
"relayMessage",
abi.Function,
"external", // mutability
false, // isConst
true, // payable
abi.Arguments{ // inputs
{Name: "target", Type: addressType},
{Name: "sender", Type: addressType},
{Name: "data", Type: bytesType},
{Name: "nonce", Type: uint256Type},
// The actual transaction on the legacy L1CrossDomainMessenger has a trailing
// proof argument but is ignored for the `XDomainCallData` encoding
},
abi.Arguments{}, // outputs
)
)
type CrossDomainMessengerSentMessageEvent struct {
Event *database.ContractEvent
BridgeMessage database.BridgeMessage
......@@ -117,7 +91,8 @@ func CrossDomainMessengerSentMessageEvents(chainSelector string, contractAddress
default:
// NOTE: We explicitly fail here since the presence of a new version means finalization
// logic needs to be updated to ensure L1 finalization can run from genesis and handle
// the changing version formats. This is meant to be a serving indicator of this.
// the changing version formats. Any unrelayed OVM1 messages that have been harcoded with
// the v1 hash format also need to be updated. This failure is a serving indicator
return nil, fmt.Errorf("expected cross domain version 0 or version 1: %d", version)
}
......
......@@ -30,12 +30,6 @@ type OptimismPortalWithdrawalFinalizedEvent struct {
Event *database.ContractEvent
}
type OptimismPortalProvenWithdrawal struct {
OutputRoot [32]byte
Timestamp *big.Int
L2OutputIndex *big.Int
}
func OptimismPortalTransactionDepositEvents(contractAddress common.Address, db *database.DB, fromHeight, toHeight *big.Int) ([]OptimismPortalTransactionDepositEvent, error) {
optimismPortalAbi, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil {
......
......@@ -25,6 +25,7 @@ const (
L1FeeVault = "0x420000000000000000000000000000000000001a"
SchemaRegistry = "0x4200000000000000000000000000000000000020"
EAS = "0x4200000000000000000000000000000000000021"
Create2Deployer = "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2"
)
var (
......@@ -47,39 +48,48 @@ var (
L1FeeVaultAddr = common.HexToAddress(L1FeeVault)
SchemaRegistryAddr = common.HexToAddress(SchemaRegistry)
EASAddr = common.HexToAddress(EAS)
Create2DeployerAddr = common.HexToAddress(Create2Deployer)
Predeploys = make(map[string]*common.Address)
Predeploys = make(map[string]*Predeploy)
PredeploysByAddress = make(map[common.Address]*Predeploy)
)
// IsProxied returns true for predeploys that will sit behind a proxy contract
func IsProxied(predeployAddr common.Address) bool {
switch predeployAddr {
case WETH9Addr:
case GovernanceTokenAddr:
default:
return true
func init() {
Predeploys["L2ToL1MessagePasser"] = &Predeploy{Address: L2ToL1MessagePasserAddr}
Predeploys["DeployerWhitelist"] = &Predeploy{Address: DeployerWhitelistAddr}
Predeploys["WETH9"] = &Predeploy{Address: WETH9Addr, ProxyDisabled: true}
Predeploys["L2CrossDomainMessenger"] = &Predeploy{Address: L2CrossDomainMessengerAddr}
Predeploys["L2StandardBridge"] = &Predeploy{Address: L2StandardBridgeAddr}
Predeploys["SequencerFeeVault"] = &Predeploy{Address: SequencerFeeVaultAddr}
Predeploys["OptimismMintableERC20Factory"] = &Predeploy{Address: OptimismMintableERC20FactoryAddr}
Predeploys["L1BlockNumber"] = &Predeploy{Address: L1BlockNumberAddr}
Predeploys["GasPriceOracle"] = &Predeploy{Address: GasPriceOracleAddr}
Predeploys["L1Block"] = &Predeploy{Address: L1BlockAddr}
Predeploys["GovernanceToken"] = &Predeploy{
Address: GovernanceTokenAddr,
ProxyDisabled: true,
Enabled: func(config DeployConfig) bool {
return config.GovernanceEnabled()
},
}
Predeploys["LegacyMessagePasser"] = &Predeploy{Address: LegacyMessagePasserAddr}
Predeploys["L2ERC721Bridge"] = &Predeploy{Address: L2ERC721BridgeAddr}
Predeploys["OptimismMintableERC721Factory"] = &Predeploy{Address: OptimismMintableERC721FactoryAddr}
Predeploys["ProxyAdmin"] = &Predeploy{Address: ProxyAdminAddr}
Predeploys["BaseFeeVault"] = &Predeploy{Address: BaseFeeVaultAddr}
Predeploys["L1FeeVault"] = &Predeploy{Address: L1FeeVaultAddr}
Predeploys["SchemaRegistry"] = &Predeploy{Address: SchemaRegistryAddr}
Predeploys["EAS"] = &Predeploy{Address: EASAddr}
Predeploys["Create2Deployer"] = &Predeploy{
Address: Create2DeployerAddr,
ProxyDisabled: true,
Enabled: func(config DeployConfig) bool {
canyonTime := config.CanyonTime(0)
return canyonTime != nil && *canyonTime == 0
},
}
return false
}
func init() {
Predeploys["L2ToL1MessagePasser"] = &L2ToL1MessagePasserAddr
Predeploys["DeployerWhitelist"] = &DeployerWhitelistAddr
Predeploys["WETH9"] = &WETH9Addr
Predeploys["L2CrossDomainMessenger"] = &L2CrossDomainMessengerAddr
Predeploys["L2StandardBridge"] = &L2StandardBridgeAddr
Predeploys["SequencerFeeVault"] = &SequencerFeeVaultAddr
Predeploys["OptimismMintableERC20Factory"] = &OptimismMintableERC20FactoryAddr
Predeploys["L1BlockNumber"] = &L1BlockNumberAddr
Predeploys["GasPriceOracle"] = &GasPriceOracleAddr
Predeploys["L1Block"] = &L1BlockAddr
Predeploys["GovernanceToken"] = &GovernanceTokenAddr
Predeploys["LegacyMessagePasser"] = &LegacyMessagePasserAddr
Predeploys["L2ERC721Bridge"] = &L2ERC721BridgeAddr
Predeploys["OptimismMintableERC721Factory"] = &OptimismMintableERC721FactoryAddr
Predeploys["ProxyAdmin"] = &ProxyAdminAddr
Predeploys["BaseFeeVault"] = &BaseFeeVaultAddr
Predeploys["L1FeeVault"] = &L1FeeVaultAddr
Predeploys["SchemaRegistry"] = &SchemaRegistryAddr
Predeploys["EAS"] = &EASAddr
for _, predeploy := range Predeploys {
PredeploysByAddress[predeploy.Address] = predeploy
}
}
package predeploys
import (
"github.com/ethereum/go-ethereum/common"
)
type DeployConfig interface {
GovernanceEnabled() bool
CanyonTime(genesisTime uint64) *uint64
}
type Predeploy struct {
Address common.Address
ProxyDisabled bool
Enabled func(config DeployConfig) bool
}
......@@ -99,8 +99,8 @@ func entrypoint(ctx *cli.Context) error {
log.Info("All predeploy proxies are set correctly")
// Check that all of the defined predeploys are set up correctly
for name, addr := range predeploys.Predeploys {
log.Info("Checking predeploy", "name", name, "address", addr.Hex())
for name, pre := range predeploys.Predeploys {
log.Info("Checking predeploy", "name", name, "address", pre.Address.Hex())
if err := checkPredeployConfig(clients.L2Client, name); err != nil {
return err
}
......@@ -112,7 +112,7 @@ func entrypoint(ctx *cli.Context) error {
func checkPredeploy(client *ethclient.Client, i uint64) error {
bigAddr := new(big.Int).Or(genesis.BigL2PredeployNamespace, new(big.Int).SetUint64(i))
addr := common.BigToAddress(bigAddr)
if !predeploys.IsProxied(addr) {
if pre, ok := predeploys.PredeploysByAddress[addr]; ok && pre.ProxyDisabled {
return nil
}
admin, err := getEIP1967AdminAddress(client, addr)
......@@ -131,10 +131,10 @@ func checkPredeployConfig(client *ethclient.Client, name string) error {
if predeploy == nil {
return fmt.Errorf("unknown predeploy %s", name)
}
p := *predeploy
p := predeploy.Address
g := new(errgroup.Group)
if predeploys.IsProxied(p) {
if !predeploy.ProxyDisabled {
// Check that an implementation is set. If the implementation has been upgraded,
// it will be considered non-standard. Ensure that there is code set at the implementation.
g.Go(func() error {
......
......@@ -32,6 +32,11 @@ type Constructor struct {
Args []interface{}
}
type SuperchainPredeploy struct {
Name string
CodeHash common.Hash
}
type Deployment struct {
Name string
Bytecode hexutil.Bytes
......
......@@ -437,6 +437,10 @@ func (d *DeployConfig) GetDeployedAddresses(hh *hardhat.Hardhat) error {
return nil
}
func (d *DeployConfig) GovernanceEnabled() bool {
return d.EnableGovernance
}
func (d *DeployConfig) RegolithTime(genesisTime uint64) *uint64 {
if d.L2GenesisRegolithTimeOffset == nil {
return nil
......
......@@ -18,13 +18,13 @@ var (
codeNamespace = common.HexToAddress("0xc0D3C0d3C0d3C0D3c0d3C0d3c0D3C0d3c0d30000")
// l2PredeployNamespace represents the namespace of L2 predeploys
l2PredeployNamespace = common.HexToAddress("0x4200000000000000000000000000000000000000")
// bigL2PredeployNamespace represents the predeploy namespace as a big.Int
// BigL2PredeployNamespace represents the predeploy namespace as a big.Int
BigL2PredeployNamespace = new(big.Int).SetBytes(l2PredeployNamespace.Bytes())
// bigCodeNamespace represents the predeploy namespace as a big.Int
bigCodeNameSpace = new(big.Int).SetBytes(codeNamespace.Bytes())
// implementationSlot represents the EIP 1967 implementation storage slot
bigCodeNamespace = new(big.Int).SetBytes(codeNamespace.Bytes())
// ImplementationSlot represents the EIP 1967 implementation storage slot
ImplementationSlot = common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
// implementationSlot represents the EIP 1967 admin storage slot
// AdminSlot represents the EIP 1967 admin storage slot
AdminSlot = common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")
)
......@@ -69,7 +69,7 @@ func AddressToCodeNamespace(addr common.Address) (common.Address, error) {
return common.Address{}, fmt.Errorf("cannot handle non predeploy: %s", addr)
}
bigAddress := new(big.Int).SetBytes(addr[18:])
num := new(big.Int).Or(bigCodeNameSpace, bigAddress)
num := new(big.Int).Or(bigCodeNamespace, bigAddress)
return common.BigToAddress(num), nil
}
......
......@@ -13,7 +13,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/eth"
)
// BuildL2DeveloperGenesis will build the L2 genesis block.
// BuildL2Genesis will build the L2 genesis block.
func BuildL2Genesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Genesis, error) {
genspec, err := NewL2Genesis(config, l1StartBlock)
if err != nil {
......@@ -50,25 +50,23 @@ func BuildL2Genesis(config *DeployConfig, l1StartBlock *types.Block) (*core.Gene
return nil, err
}
for name, predeploy := range predeploys.Predeploys {
addr := *predeploy
if addr == predeploys.GovernanceTokenAddr && !config.EnableGovernance {
// there is no governance token configured, so skip the governance token predeploy
log.Warn("Governance is not enabled, skipping governance token predeploy.")
if predeploy.Enabled != nil && !predeploy.Enabled(config) {
log.Warn("Skipping disabled predeploy.", "name", name, "address", predeploy.Address)
continue
}
codeAddr := addr
if predeploys.IsProxied(addr) {
codeAddr, err = AddressToCodeNamespace(addr)
codeAddr := predeploy.Address
if !predeploy.ProxyDisabled {
codeAddr, err = AddressToCodeNamespace(predeploy.Address)
if err != nil {
return nil, fmt.Errorf("error converting to code namespace: %w", err)
}
db.CreateAccount(codeAddr)
db.SetState(addr, ImplementationSlot, eth.AddressAsLeftPaddedHash(codeAddr))
log.Info("Set proxy", "name", name, "address", addr, "implementation", codeAddr)
} else {
db.DeleteState(addr, AdminSlot)
db.SetState(predeploy.Address, ImplementationSlot, eth.AddressAsLeftPaddedHash(codeAddr))
log.Info("Set proxy", "name", name, "address", predeploy.Address, "implementation", codeAddr)
} else if db.Exist(predeploy.Address) {
db.DeleteState(predeploy.Address, AdminSlot)
}
if err := setupPredeploy(db, deployResults, storage, name, addr, codeAddr); err != nil {
if err := setupPredeploy(db, deployResults, storage, name, predeploy.Address, codeAddr); err != nil {
return nil, err
}
code := db.GetCode(codeAddr)
......
......@@ -18,6 +18,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-chain-ops/immutables"
"github.com/ethereum-optimism/optimism/op-service/eth"
)
......@@ -48,14 +49,14 @@ func testBuildL2Genesis(t *testing.T, config *genesis.DeployConfig) *core.Genesi
require.NoError(t, err)
for name, predeploy := range predeploys.Predeploys {
addr := *predeploy
addr := predeploy.Address
account, ok := gen.Alloc[addr]
require.Equal(t, true, ok, name)
require.Greater(t, len(account.Code), 0)
adminSlot, ok := account.Storage[genesis.AdminSlot]
isProxy := predeploys.IsProxied(addr) ||
isProxy := !predeploy.ProxyDisabled ||
(!config.EnableGovernance && addr == predeploys.GovernanceTokenAddr)
if isProxy {
require.Equal(t, true, ok, name)
......@@ -73,6 +74,10 @@ func testBuildL2Genesis(t *testing.T, config *genesis.DeployConfig) *core.Genesi
require.Equal(t, common.Big1, gen.Alloc[addr].Balance)
}
create2Deployer := gen.Alloc[predeploys.Create2DeployerAddr]
codeHash := crypto.Keccak256Hash(create2Deployer.Code)
require.Equal(t, codeHash, immutables.Create2DeployerCodeHash)
if writeFile {
file, _ := json.MarshalIndent(gen, "", " ")
_ = os.WriteFile("genesis.json", file, 0644)
......@@ -86,7 +91,7 @@ func TestBuildL2MainnetGenesis(t *testing.T) {
config.EnableGovernance = true
config.FundDevAccounts = false
gen := testBuildL2Genesis(t, config)
require.Equal(t, 2322, len(gen.Alloc))
require.Equal(t, 2323, len(gen.Alloc))
}
func TestBuildL2MainnetNoGovernanceGenesis(t *testing.T) {
......@@ -95,5 +100,5 @@ func TestBuildL2MainnetNoGovernanceGenesis(t *testing.T) {
config.EnableGovernance = false
config.FundDevAccounts = false
gen := testBuildL2Genesis(t, config)
require.Equal(t, 2322, len(gen.Alloc))
require.Equal(t, 2323, len(gen.Alloc))
}
......@@ -55,8 +55,7 @@ func setProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace *big.Int
}
// SetPrecompileBalances will set a single wei at each precompile address.
// This is an optimization to make calling them cheaper. This should only
// be used for devnets.
// This is an optimization to make calling them cheaper.
func SetPrecompileBalances(db vm.StateDB) {
for i := 0; i < PrecompileCount; i++ {
addr := common.BytesToAddress([]byte{byte(i)})
......
......@@ -41,5 +41,8 @@
"enableGovernance": true,
"governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism",
"governanceTokenOwner": "0x0000000000000000000000000000000000000333"
"governanceTokenOwner": "0x0000000000000000000000000000000000000333",
"l2GenesisRegolithTimeOffset": "0x0",
"l2GenesisCanyonTimeOffset": "0x0"
}
......@@ -5,16 +5,17 @@ import (
"fmt"
"math/big"
"github.com/ethereum-optimism/superchain-registry/superchain"
"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/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/deployer"
"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/common/hexutil"
)
// ImmutableValues represents the values to be set in immutable code.
......@@ -26,6 +27,8 @@ type ImmutableValues map[string]any
// contracts.
type ImmutableConfig map[string]ImmutableValues
var Create2DeployerCodeHash = common.HexToHash("0xb0550b5b431e30d38000efb7107aaa0ade03d48a7198a140edda9d27134468b2")
// Check does a sanity check that the specific values that
// Optimism uses are set inside of the ImmutableConfig.
func (i ImmutableConfig) Check() error {
......@@ -152,13 +155,19 @@ func BuildOptimism(immutable ImmutableConfig) (DeploymentResults, error) {
Name: "SchemaRegistry",
},
}
return BuildL2(deployments)
superchainPredeploys := []deployer.SuperchainPredeploy{
{
Name: "Create2Deployer",
CodeHash: Create2DeployerCodeHash,
},
}
return BuildL2(deployments, superchainPredeploys)
}
// BuildL2 will deploy contracts to a simulated backend so that their immutables
// can be properly set. The bytecode returned in the results is suitable to be
// inserted into the state via state surgery.
func BuildL2(constructors []deployer.Constructor) (DeploymentResults, error) {
func BuildL2(constructors []deployer.Constructor, superchainPredeploys []deployer.SuperchainPredeploy) (DeploymentResults, error) {
log.Info("Creating L2 state")
deployments, err := deployer.Deploy(deployer.NewL2Backend(), constructors, l2Deployer)
if err != nil {
......@@ -168,6 +177,13 @@ func BuildL2(constructors []deployer.Constructor) (DeploymentResults, error) {
for _, dep := range deployments {
results[dep.Name] = dep.Bytecode
}
for _, dep := range superchainPredeploys {
code, err := superchain.LoadContractBytecode(superchain.Hash(dep.CodeHash))
if err != nil {
return nil, err
}
results[dep.Name] = code
}
return results, nil
}
......
......@@ -65,6 +65,7 @@ func TestBuildOptimism(t *testing.T) {
"LegacyERC20ETH": true,
"EAS": true,
"SchemaRegistry": true,
"Create2Deployer": true,
}
// Only the exact contracts that we care about are being
......
......@@ -61,6 +61,10 @@ func (db *MemoryStateDB) CreateAccount(addr common.Address) {
db.rw.Lock()
defer db.rw.Unlock()
db.createAccount(addr)
}
func (db *MemoryStateDB) createAccount(addr common.Address) {
if _, ok := db.genesis.Alloc[addr]; !ok {
db.genesis.Alloc[addr] = core.GenesisAccount{
Code: []byte{},
......@@ -69,7 +73,6 @@ func (db *MemoryStateDB) CreateAccount(addr common.Address) {
Nonce: 0,
}
}
}
func (db *MemoryStateDB) SubBalance(addr common.Address, amount *big.Int) {
......@@ -165,6 +168,8 @@ func (db *MemoryStateDB) SetCode(addr common.Address, code []byte) {
db.rw.Lock()
defer db.rw.Unlock()
db.createAccount(addr)
account, ok := db.genesis.Alloc[addr]
if !ok {
return
......
......@@ -32,16 +32,16 @@ func NewDisputeGameFactoryContract(addr common.Address, caller *batching.MultiCa
}, nil
}
func (f *DisputeGameFactoryContract) GetGameCount(ctx context.Context, blockNum uint64) (uint64, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockByNumber(blockNum), f.contract.Call(methodGameCount))
func (f *DisputeGameFactoryContract) GetGameCount(ctx context.Context, blockHash common.Hash) (uint64, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockByHash(blockHash), f.contract.Call(methodGameCount))
if err != nil {
return 0, fmt.Errorf("failed to load game count: %w", err)
}
return result.GetBigInt(0).Uint64(), nil
}
func (f *DisputeGameFactoryContract) GetGame(ctx context.Context, idx uint64, blockNum uint64) (types.GameMetadata, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockByNumber(blockNum), f.contract.Call(methodGameAtIndex, new(big.Int).SetUint64(idx)))
func (f *DisputeGameFactoryContract) GetGame(ctx context.Context, idx uint64, blockHash common.Hash) (types.GameMetadata, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockByHash(blockHash), f.contract.Call(methodGameAtIndex, new(big.Int).SetUint64(idx)))
if err != nil {
return types.GameMetadata{}, fmt.Errorf("failed to load game %v: %w", idx, err)
}
......
......@@ -18,7 +18,7 @@ var (
)
func TestDisputeGameFactorySimpleGetters(t *testing.T) {
blockNum := uint64(23)
blockHash := common.Hash{0xbb, 0xcd}
tests := []struct {
method string
args []interface{}
......@@ -31,7 +31,7 @@ func TestDisputeGameFactorySimpleGetters(t *testing.T) {
result: big.NewInt(9876),
expected: uint64(9876),
call: func(game *DisputeGameFactoryContract) (any, error) {
return game.GetGameCount(context.Background(), blockNum)
return game.GetGameCount(context.Background(), blockHash)
},
},
}
......@@ -39,7 +39,7 @@ func TestDisputeGameFactorySimpleGetters(t *testing.T) {
test := test
t.Run(test.method, func(t *testing.T) {
stubRpc, factory := setupDisputeGameFactoryTest(t)
stubRpc.SetResponse(factoryAddr, test.method, batching.BlockByNumber(blockNum), nil, []interface{}{test.result})
stubRpc.SetResponse(factoryAddr, test.method, batching.BlockByHash(blockHash), nil, []interface{}{test.result})
status, err := test.call(factory)
require.NoError(t, err)
expected := test.expected
......@@ -52,7 +52,7 @@ func TestDisputeGameFactorySimpleGetters(t *testing.T) {
}
func TestLoadGame(t *testing.T) {
blockNum := uint64(23)
blockHash := common.Hash{0xbb, 0xce}
stubRpc, factory := setupDisputeGameFactoryTest(t)
game0 := types.GameMetadata{
GameType: 0,
......@@ -71,18 +71,18 @@ func TestLoadGame(t *testing.T) {
}
expectedGames := []types.GameMetadata{game0, game1, game2}
for idx, expected := range expectedGames {
expectGetGame(stubRpc, idx, blockNum, expected)
actual, err := factory.GetGame(context.Background(), uint64(idx), blockNum)
expectGetGame(stubRpc, idx, blockHash, expected)
actual, err := factory.GetGame(context.Background(), uint64(idx), blockHash)
require.NoError(t, err)
require.Equal(t, expected, actual)
}
}
func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockNum uint64, game types.GameMetadata) {
func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockHash common.Hash, game types.GameMetadata) {
stubRpc.SetResponse(
factoryAddr,
methodGameAtIndex,
batching.BlockByNumber(blockNum),
batching.BlockByHash(blockHash),
[]interface{}{big.NewInt(int64(idx))},
[]interface{}{
game.GameType,
......
......@@ -26,6 +26,8 @@ var (
alphabetGameType = uint8(255)
)
type CloseFunc func()
type Registry interface {
RegisterGameType(gameType uint8, creator scheduler.PlayerCreator)
}
......@@ -38,16 +40,27 @@ func RegisterGameTypes(
cfg *config.Config,
txMgr txmgr.TxManager,
client *ethclient.Client,
) {
) (CloseFunc, error) {
var closer CloseFunc
var l2Client *ethclient.Client
if cfg.TraceTypeEnabled(config.TraceTypeCannon) || cfg.TraceTypeEnabled(config.TraceTypeOutputCannon) {
l2, err := ethclient.DialContext(ctx, cfg.CannonL2)
if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
}
l2Client = l2
closer = l2Client.Close
}
if cfg.TraceTypeEnabled(config.TraceTypeOutputCannon) {
registerOutputCannon(registry, ctx, logger, m, cfg, txMgr, client)
registerOutputCannon(registry, ctx, logger, m, cfg, txMgr, client, l2Client)
}
if cfg.TraceTypeEnabled(config.TraceTypeCannon) {
registerCannon(registry, ctx, logger, m, cfg, txMgr, client)
registerCannon(registry, ctx, logger, m, cfg, txMgr, client, l2Client)
}
if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
registerAlphabet(registry, ctx, logger, m, cfg, txMgr, client)
}
return closer, nil
}
func registerOutputCannon(
......@@ -57,7 +70,8 @@ func registerOutputCannon(
m metrics.Metricer,
cfg *config.Config,
txMgr txmgr.TxManager,
client *ethclient.Client) {
client *ethclient.Client,
l2Client cannon.L2HeaderSource) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) {
logger := logger.New("game", addr)
// TODO(client-pod#43): Updated contracts should expose this as the pre and post state blocks
......@@ -65,7 +79,7 @@ func registerOutputCannon(
if err != nil {
return nil, nil, err
}
accessor, err := outputs.NewOutputCannonTraceAccessor(ctx, logger, m, cfg, contract, dir, gameDepth, agreed.L2BlockNumber.Uint64(), disputed.L2BlockNumber.Uint64())
accessor, err := outputs.NewOutputCannonTraceAccessor(ctx, logger, m, cfg, l2Client, contract, dir, gameDepth, agreed.L2BlockNumber.Uint64(), disputed.L2BlockNumber.Uint64())
if err != nil {
return nil, nil, err
}
......@@ -88,13 +102,15 @@ func registerCannon(
m metrics.Metricer,
cfg *config.Config,
txMgr txmgr.TxManager,
client *ethclient.Client) {
client *ethclient.Client,
l2Client cannon.L2HeaderSource) {
resourceCreator := func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (faultTypes.TraceAccessor, gameValidator, error) {
logger := logger.New("game", addr)
provider, err := cannon.NewTraceProvider(ctx, logger, m, cfg, contract, faultTypes.NoLocalContext, dir, gameDepth)
localInputs, err := cannon.FetchLocalInputs(ctx, contract, l2Client)
if err != nil {
return nil, nil, fmt.Errorf("create cannon trace provider: %w", err)
return nil, nil, fmt.Errorf("failed to fetch cannon local inputs: %w", err)
}
provider := cannon.NewTraceProvider(logger, m, cfg, faultTypes.NoLocalContext, localInputs, dir, gameDepth)
validator := func(ctx context.Context, contract *contracts.FaultDisputeGameContract) error {
return ValidateAbsolutePrestate(ctx, provider, contract)
}
......
......@@ -18,8 +18,7 @@ type LocalGameInputs struct {
L2BlockNumber *big.Int
}
type L2DataSource interface {
ChainID(context.Context) (*big.Int, error)
type L2HeaderSource interface {
HeaderByNumber(context.Context, *big.Int) (*ethtypes.Header, error)
}
......@@ -32,15 +31,15 @@ type GameInputsSource interface {
GetProposals(ctx context.Context) (agreed contracts.Proposal, disputed contracts.Proposal, err error)
}
func fetchLocalInputs(ctx context.Context, caller GameInputsSource, l2Client L2DataSource) (LocalGameInputs, error) {
func FetchLocalInputs(ctx context.Context, caller GameInputsSource, l2Client L2HeaderSource) (LocalGameInputs, error) {
agreedOutput, claimedOutput, err := caller.GetProposals(ctx)
if err != nil {
return LocalGameInputs{}, fmt.Errorf("fetch proposals: %w", err)
}
return fetchLocalInputsFromProposals(ctx, caller, l2Client, agreedOutput, claimedOutput)
return FetchLocalInputsFromProposals(ctx, caller, l2Client, agreedOutput, claimedOutput)
}
func fetchLocalInputsFromProposals(ctx context.Context, caller L1HeadSource, l2Client L2DataSource, agreedOutput contracts.Proposal, claimedOutput contracts.Proposal) (LocalGameInputs, error) {
func FetchLocalInputsFromProposals(ctx context.Context, caller L1HeadSource, l2Client L2HeaderSource, agreedOutput contracts.Proposal, claimedOutput contracts.Proposal) (LocalGameInputs, error) {
l1Head, err := caller.GetL1Head(ctx)
if err != nil {
return LocalGameInputs{}, fmt.Errorf("fetch L1 head: %w", err)
......
......@@ -32,7 +32,7 @@ func TestFetchLocalInputs(t *testing.T) {
},
}
inputs, err := fetchLocalInputs(ctx, contract, l2Client)
inputs, err := FetchLocalInputs(ctx, contract, l2Client)
require.NoError(t, err)
require.Equal(t, contract.l1Head, inputs.L1Head)
......@@ -62,7 +62,7 @@ func TestFetchLocalInputsFromProposals(t *testing.T) {
},
}
inputs, err := fetchLocalInputsFromProposals(ctx, contract, l2Client, agreed, claimed)
inputs, err := FetchLocalInputsFromProposals(ctx, contract, l2Client, agreed, claimed)
require.NoError(t, err)
require.Equal(t, contract.l1Head, inputs.L1Head)
......@@ -91,11 +91,11 @@ type mockL2DataSource struct {
header ethtypes.Header
}
func (s *mockL2DataSource) ChainID(ctx context.Context) (*big.Int, error) {
func (s *mockL2DataSource) ChainID(_ context.Context) (*big.Int, error) {
return s.chainID, nil
}
func (s *mockL2DataSource) HeaderByNumber(ctx context.Context, num *big.Int) (*ethtypes.Header, error) {
func (s *mockL2DataSource) HeaderByNumber(_ context.Context, num *big.Int) (*ethtypes.Header, error) {
if s.header.Number.Cmp(num) == 0 {
return &s.header, nil
}
......
......@@ -9,12 +9,10 @@ import (
"path/filepath"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
......@@ -56,44 +54,7 @@ type CannonTraceProvider struct {
lastStep uint64
}
func NewTraceProvider(ctx context.Context, logger log.Logger, m CannonMetricer, cfg *config.Config, gameContract *contracts.FaultDisputeGameContract, localContext common.Hash, dir string, gameDepth uint64) (*CannonTraceProvider, error) {
l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2)
if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
}
defer l2Client.Close() // Not needed after fetching the inputs
localInputs, err := fetchLocalInputs(ctx, gameContract, l2Client)
if err != nil {
return nil, fmt.Errorf("fetch local game inputs: %w", err)
}
return NewTraceProviderFromInputs(logger, m, cfg, localContext, localInputs, dir, gameDepth), nil
}
func NewTraceProviderFromProposals(
ctx context.Context,
logger log.Logger,
m CannonMetricer,
cfg *config.Config,
gameContract *contracts.FaultDisputeGameContract,
localContext common.Hash,
agreed contracts.Proposal,
claimed contracts.Proposal,
dir string,
gameDepth uint64,
) (*CannonTraceProvider, error) {
l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2)
if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
}
defer l2Client.Close() // Not needed after fetching the inputs
localInputs, err := fetchLocalInputsFromProposals(ctx, gameContract, l2Client, agreed, claimed)
if err != nil {
return nil, fmt.Errorf("fetch local game inputs: %w", err)
}
return NewTraceProviderFromInputs(logger, m, cfg, localContext, localInputs, dir, gameDepth), nil
}
func NewTraceProviderFromInputs(logger log.Logger, m CannonMetricer, cfg *config.Config, localContext common.Hash, localInputs LocalGameInputs, dir string, gameDepth uint64) *CannonTraceProvider {
func NewTraceProvider(logger log.Logger, m CannonMetricer, cfg *config.Config, localContext common.Hash, localInputs LocalGameInputs, dir string, gameDepth uint64) *CannonTraceProvider {
return &CannonTraceProvider{
logger: logger,
dir: dir,
......
......@@ -21,6 +21,7 @@ func NewOutputCannonTraceAccessor(
logger log.Logger,
m metrics.Metricer,
cfg *config.Config,
l2Client cannon.L2HeaderSource,
contract *contracts.FaultDisputeGameContract,
dir string,
gameDepth uint64,
......@@ -38,10 +39,11 @@ func NewOutputCannonTraceAccessor(
cannonCreator := func(ctx context.Context, localContext common.Hash, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) {
logger := logger.New("pre", agreed.OutputRoot, "post", claimed.OutputRoot, "localContext", localContext)
subdir := filepath.Join(dir, localContext.Hex())
provider, err := cannon.NewTraceProviderFromProposals(ctx, logger, m, cfg, contract, localContext, agreed, claimed, subdir, bottomDepth)
localInputs, err := cannon.FetchLocalInputsFromProposals(ctx, contract, l2Client, agreed, claimed)
if err != nil {
return nil, fmt.Errorf("failed to create cannon trace provider: %w", err)
return nil, fmt.Errorf("failed to fetch cannon local inputs: %w", err)
}
provider := cannon.NewTraceProvider(logger, m, cfg, localContext, localInputs, subdir, bottomDepth)
return provider, nil
}
......
......@@ -2,21 +2,17 @@ package loader
import (
"context"
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
)
var (
ErrMissingBlockNumber = errors.New("game loader missing block number")
"github.com/ethereum/go-ethereum/common"
)
// MinimalDisputeGameFactoryCaller is a minimal interface around [bindings.DisputeGameFactoryCaller].
// This needs to be updated if the [bindings.DisputeGameFactoryCaller] interface changes.
type MinimalDisputeGameFactoryCaller interface {
GetGameCount(ctx context.Context, blockNum uint64) (uint64, error)
GetGame(ctx context.Context, idx uint64, blockNum uint64) (types.GameMetadata, error)
GetGameCount(ctx context.Context, blockHash common.Hash) (uint64, error)
GetGame(ctx context.Context, idx uint64, blockHash common.Hash) (types.GameMetadata, error)
}
type GameLoader struct {
......@@ -31,15 +27,15 @@ func NewGameLoader(caller MinimalDisputeGameFactoryCaller) *GameLoader {
}
// FetchAllGamesAtBlock fetches all dispute games from the factory at a given block number.
func (l *GameLoader) FetchAllGamesAtBlock(ctx context.Context, earliestTimestamp uint64, blockNumber uint64) ([]types.GameMetadata, error) {
gameCount, err := l.caller.GetGameCount(ctx, blockNumber)
func (l *GameLoader) FetchAllGamesAtBlock(ctx context.Context, earliestTimestamp uint64, blockHash common.Hash) ([]types.GameMetadata, error) {
gameCount, err := l.caller.GetGameCount(ctx, blockHash)
if err != nil {
return nil, fmt.Errorf("failed to fetch game count: %w", err)
}
games := make([]types.GameMetadata, 0, gameCount)
for i := gameCount; i > 0; i-- {
game, err := l.caller.GetGame(ctx, i-1, blockNumber)
game, err := l.caller.GetGame(ctx, i-1, blockHash)
if err != nil {
return nil, fmt.Errorf("failed to fetch game at index %d: %w", i-1, err)
}
......
......@@ -24,39 +24,39 @@ func TestGameLoader_FetchAllGames(t *testing.T) {
name string
caller *mockMinimalDisputeGameFactoryCaller
earliest uint64
blockNumber uint64
blockHash common.Hash
expectedErr error
expectedLen int
}{
{
name: "success",
caller: newMockMinimalDisputeGameFactoryCaller(10, false, false),
blockNumber: 1,
blockHash: common.Hash{0x01},
expectedLen: 10,
},
{
name: "expired game ignored",
caller: newMockMinimalDisputeGameFactoryCaller(10, false, false),
earliest: 500,
blockNumber: 1,
blockHash: common.Hash{0x01},
expectedLen: 5,
},
{
name: "game count error",
caller: newMockMinimalDisputeGameFactoryCaller(10, true, false),
blockNumber: 1,
blockHash: common.Hash{0x01},
expectedErr: gameCountErr,
},
{
name: "game index error",
caller: newMockMinimalDisputeGameFactoryCaller(10, false, true),
blockNumber: 1,
blockHash: common.Hash{0x01},
expectedErr: gameIndexErr,
},
{
name: "no games",
caller: newMockMinimalDisputeGameFactoryCaller(0, false, false),
blockNumber: 1,
name: "no games",
caller: newMockMinimalDisputeGameFactoryCaller(0, false, false),
blockHash: common.Hash{0x01},
},
}
......@@ -67,7 +67,7 @@ func TestGameLoader_FetchAllGames(t *testing.T) {
t.Parallel()
loader := NewGameLoader(test.caller)
games, err := loader.FetchAllGamesAtBlock(context.Background(), test.earliest, test.blockNumber)
games, err := loader.FetchAllGamesAtBlock(context.Background(), test.earliest, test.blockHash)
require.ErrorIs(t, err, test.expectedErr)
require.Len(t, games, test.expectedLen)
expectedGames := test.caller.games
......@@ -138,7 +138,7 @@ func newMockMinimalDisputeGameFactoryCaller(count uint64, gameCountErr bool, ind
}
}
func (m *mockMinimalDisputeGameFactoryCaller) GetGameCount(_ context.Context, blockNum uint64) (uint64, error) {
func (m *mockMinimalDisputeGameFactoryCaller) GetGameCount(_ context.Context, _ common.Hash) (uint64, error) {
if m.gameCountErr {
return 0, gameCountErr
}
......@@ -146,7 +146,7 @@ func (m *mockMinimalDisputeGameFactoryCaller) GetGameCount(_ context.Context, bl
return m.gameCount, nil
}
func (m *mockMinimalDisputeGameFactoryCaller) GetGame(_ context.Context, index uint64, blockNum uint64) (types.GameMetadata, error) {
func (m *mockMinimalDisputeGameFactoryCaller) GetGame(_ context.Context, index uint64, _ common.Hash) (types.GameMetadata, error) {
if m.indexErrors[index] {
return struct {
GameType uint8
......
......@@ -23,7 +23,7 @@ type blockNumberFetcher func(ctx context.Context) (uint64, error)
// gameSource loads information about the games available to play
type gameSource interface {
FetchAllGamesAtBlock(ctx context.Context, earliest uint64, blockNumber uint64) ([]types.GameMetadata, error)
FetchAllGamesAtBlock(ctx context.Context, earliest uint64, blockHash common.Hash) ([]types.GameMetadata, error)
}
type gameScheduler interface {
......@@ -101,8 +101,8 @@ func (m *gameMonitor) minGameTimestamp() uint64 {
return 0
}
func (m *gameMonitor) progressGames(ctx context.Context, blockNum uint64) error {
games, err := m.source.FetchAllGamesAtBlock(ctx, m.minGameTimestamp(), blockNum)
func (m *gameMonitor) progressGames(ctx context.Context, blockHash common.Hash) error {
games, err := m.source.FetchAllGamesAtBlock(ctx, m.minGameTimestamp(), blockHash)
if err != nil {
return fmt.Errorf("failed to load games: %w", err)
}
......@@ -123,7 +123,7 @@ func (m *gameMonitor) progressGames(ctx context.Context, blockNum uint64) error
}
func (m *gameMonitor) onNewL1Head(ctx context.Context, sig eth.L1BlockRef) {
if err := m.progressGames(ctx, sig.Number); err != nil {
if err := m.progressGames(ctx, sig.Hash); err != nil {
m.logger.Error("Failed to progress games", "err", err)
}
}
......
......@@ -145,7 +145,7 @@ func TestMonitorCreateAndProgressGameAgents(t *testing.T) {
addr2 := common.Address{0xbb}
source.games = []types.GameMetadata{newFDG(addr1, 9999), newFDG(addr2, 9999)}
require.NoError(t, monitor.progressGames(context.Background(), uint64(1)))
require.NoError(t, monitor.progressGames(context.Background(), common.Hash{0x01}))
require.Len(t, sched.scheduled, 1)
require.Equal(t, []common.Address{addr1, addr2}, sched.scheduled[0])
......@@ -157,7 +157,7 @@ func TestMonitorOnlyScheduleSpecifiedGame(t *testing.T) {
monitor, source, sched, _ := setupMonitorTest(t, []common.Address{addr2})
source.games = []types.GameMetadata{newFDG(addr1, 9999), newFDG(addr2, 9999)}
require.NoError(t, monitor.progressGames(context.Background(), uint64(1)))
require.NoError(t, monitor.progressGames(context.Background(), common.Hash{0x01}))
require.Len(t, sched.scheduled, 1)
require.Equal(t, []common.Address{addr2}, sched.scheduled[0])
......@@ -232,7 +232,7 @@ type stubGameSource struct {
func (s *stubGameSource) FetchAllGamesAtBlock(
ctx context.Context,
earliest uint64,
blockNumber uint64,
blockHash common.Hash,
) ([]types.GameMetadata, error) {
return s.games, nil
}
......
......@@ -34,6 +34,8 @@ type Service struct {
monitor *gameMonitor
sched *scheduler.Scheduler
faultGamesCloser fault.CloseFunc
txMgr *txmgr.SimpleTxManager
loader *loader.GameLoader
......@@ -83,8 +85,10 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
if err := s.initGameLoader(cfg); err != nil {
return err
}
if err := s.initScheduler(ctx, cfg); err != nil {
return err
}
s.initScheduler(ctx, cfg)
s.initMonitor(cfg)
s.metrics.RecordInfo(version.SimpleWithMeta)
......@@ -162,12 +166,17 @@ func (s *Service) initGameLoader(cfg *config.Config) error {
return nil
}
func (s *Service) initScheduler(ctx context.Context, cfg *config.Config) {
func (s *Service) initScheduler(ctx context.Context, cfg *config.Config) error {
gameTypeRegistry := registry.NewGameTypeRegistry()
fault.RegisterGameTypes(gameTypeRegistry, ctx, s.logger, s.metrics, cfg, s.txMgr, s.l1Client)
closer, err := fault.RegisterGameTypes(gameTypeRegistry, ctx, s.logger, s.metrics, cfg, s.txMgr, s.l1Client)
if err != nil {
return err
}
s.faultGamesCloser = closer
disk := newDiskManager(cfg.Datadir)
s.sched = scheduler.NewScheduler(s.logger, s.metrics, disk, cfg.MaxConcurrency, gameTypeRegistry.CreatePlayer)
return nil
}
func (s *Service) initMonitor(cfg *config.Config) {
......@@ -200,6 +209,9 @@ func (s *Service) Stop(ctx context.Context) error {
if s.monitor != nil {
s.monitor.StopMonitoring()
}
if s.faultGamesCloser != nil {
s.faultGamesCloser()
}
if s.pprofSrv != nil {
if err := s.pprofSrv.Stop(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to close pprof server: %w", err))
......
......@@ -47,8 +47,12 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol
maxDepth := g.MaxDepth(ctx)
gameContract, err := contracts.NewFaultDisputeGameContract(g.addr, batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize))
g.require.NoError(err, "Create game contract bindings")
provider, err := cannon.NewTraceProvider(ctx, logger, metrics.NoopMetrics, cfg, gameContract, types.NoLocalContext, filepath.Join(cfg.Datadir, "honest"), uint64(maxDepth))
g.require.NoError(err, "create cannon trace provider")
l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2)
g.require.NoErrorf(err, "dial l2 client %v", cfg.CannonL2)
defer l2Client.Close() // Not needed after fetching the inputs
localInputs, err := cannon.FetchLocalInputs(ctx, gameContract, l2Client)
g.require.NoError(err, "fetch cannon local inputs")
provider := cannon.NewTraceProvider(logger, metrics.NoopMetrics, cfg, types.NoLocalContext, localInputs, filepath.Join(cfg.Datadir, "honest"), uint64(maxDepth))
return &HonestHelper{
t: g.t,
......
......@@ -221,7 +221,7 @@ func (h *FactoryHelper) StartCannonGameWithCorrectRoot(ctx context.Context, roll
maxDepth, err := gameImpl.MAXGAMEDEPTH(opts)
h.require.NoError(err, "fetch max game depth")
provider := cannon.NewTraceProviderFromInputs(
provider := cannon.NewTraceProvider(
testlog.Logger(h.t, log.LvlInfo).New("role", "CorrectTrace"),
metrics.NoopMetrics,
cfg,
......
......@@ -126,7 +126,7 @@ func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, al
engine := derivationPipeline
meteredEngine := NewMeteredEngine(cfg, engine, metrics, log)
sequencer := NewSequencer(log, cfg, meteredEngine, attrBuilder, findL1Origin, metrics)
driverCtx, driverCancel := context.WithCancel(context.Background())
return &Driver{
l1State: l1State,
derivation: derivationPipeline,
......@@ -138,7 +138,8 @@ func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, al
sequencerNotifs: sequencerStateListener,
config: cfg,
driverConfig: driverCfg,
done: make(chan struct{}),
driverCtx: driverCtx,
driverCancel: driverCancel,
log: log,
snapshotLog: snapshotLog,
l1: l1,
......
......@@ -86,9 +86,11 @@ type Driver struct {
metrics Metrics
log log.Logger
snapshotLog log.Logger
done chan struct{}
wg gosync.WaitGroup
driverCtx context.Context
driverCancel context.CancelFunc
}
// Start starts up the state loop.
......@@ -118,7 +120,7 @@ func (s *Driver) Start() error {
}
func (s *Driver) Close() error {
s.done <- struct{}{}
s.driverCancel()
s.wg.Wait()
return nil
}
......@@ -167,9 +169,9 @@ func (s *Driver) OnUnsafeL2Payload(ctx context.Context, payload *eth.ExecutionPa
func (s *Driver) eventLoop() {
defer s.wg.Done()
s.log.Info("State loop started")
defer s.log.Info("State loop returned")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
defer s.driverCancel()
// stepReqCh is used to request that the driver attempts to step forward by one L1 block.
stepReqCh := make(chan struct{}, 1)
......@@ -230,6 +232,10 @@ func (s *Driver) eventLoop() {
lastUnsafeL2 := s.derivation.UnsafeL2Head()
for {
if s.driverCtx.Err() != nil { // don't try to schedule/handle more work when we are closing.
return
}
// If we are sequencing, and the L1 state is ready, update the trigger for the next sequencer action.
// This may adjust at any time based on fork-choice changes or previous errors.
// And avoid sequencing if the derivation pipeline indicates the engine is not ready.
......@@ -266,7 +272,7 @@ func (s *Driver) eventLoop() {
select {
case <-sequencerCh:
payload, err := s.sequencer.RunNextSequencerAction(ctx)
payload, err := s.sequencer.RunNextSequencerAction(s.driverCtx)
if err != nil {
s.log.Error("Sequencer critical error", "err", err)
return
......@@ -274,7 +280,7 @@ func (s *Driver) eventLoop() {
if s.network != nil && payload != nil {
// Publishing of unsafe data via p2p is optional.
// Errors are not severe enough to change/halt sequencing but should be logged and metered.
if err := s.network.PublishL2Payload(ctx, payload); err != nil {
if err := s.network.PublishL2Payload(s.driverCtx, payload); err != nil {
s.log.Warn("failed to publish newly created block", "id", payload.ID(), "err", err)
s.metrics.RecordPublishingError()
}
......@@ -282,7 +288,7 @@ func (s *Driver) eventLoop() {
planSequencerAction() // schedule the next sequencer action to keep the sequencing looping
case <-altSyncTicker.C:
// Check if there is a gap in the current unsafe payload queue.
ctx, cancel := context.WithTimeout(ctx, time.Second*2)
ctx, cancel := context.WithTimeout(s.driverCtx, time.Second*2)
err := s.checkForGapInUnsafeQueue(ctx)
cancel()
if err != nil {
......@@ -311,7 +317,7 @@ func (s *Driver) eventLoop() {
case <-stepReqCh:
s.metrics.SetDerivationIdle(false)
s.log.Debug("Derivation process step", "onto_origin", s.derivation.Origin(), "attempts", stepAttempts)
err := s.derivation.Step(context.Background())
err := s.derivation.Step(s.driverCtx)
stepAttempts += 1 // count as attempt by default. We reset to 0 if we are making healthy progress.
if err == io.EOF {
s.log.Debug("Derivation process went idle", "progress", s.derivation.Origin(), "err", err)
......@@ -383,12 +389,12 @@ func (s *Driver) eventLoop() {
s.driverConfig.SequencerStopped = true
// Cancel any inflight block building. If we don't cancel this, we can resume sequencing an old block
// even if we've received new unsafe heads in the interim, causing us to introduce a re-org.
s.sequencer.CancelBuildingBlock(ctx)
s.sequencer.CancelBuildingBlock(s.driverCtx)
respCh <- hashAndError{hash: s.derivation.UnsafeL2Head().Hash}
}
case respCh := <-s.sequencerActive:
respCh <- !s.driverConfig.SequencerStopped
case <-s.done:
case <-s.driverCtx.Done():
return
}
}
......
# @eth-optimism/drippie-mon
## 0.5.5
### Patch Changes
- [#8306](https://github.com/ethereum-optimism/optimism/pull/8306) [`dcb252917`](https://github.com/ethereum-optimism/optimism/commit/dcb25291768ec0f2386486619971d5cd66fbb409) Thanks [@protolambda](https://github.com/protolambda)! - Fixed bug with custom chains not being able to set a custom portal address
## 0.5.4
### Patch Changes
......
{
"private": true,
"name": "@eth-optimism/chain-mon",
"version": "0.5.4",
"version": "0.5.5",
"description": "[Optimism] Chain monitoring services",
"main": "dist/index",
"types": "dist/index",
......@@ -63,6 +63,6 @@
"@nomiclabs/hardhat-waffle": "^2.0.6",
"hardhat": "^2.19.1",
"ts-node": "^10.9.1",
"tsx": "^4.3.0"
"tsx": "^4.5.0"
}
}
......@@ -147,11 +147,10 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
contracts.OptimismPortal = portalAddress
this.logger.info('fetching L2OutputOracle contract from OptimismPortal')
const opts = {
portalAddress,
const portalContract = getOEContract('OptimismPortal', l2ChainId, {
address: portalAddress,
signerOrProvider: this.options.l1RpcProvider,
}
const portalContract = getOEContract('OptimismPortal', l2ChainId, opts)
})
contracts.L2OutputOracle = await portalContract.L2_ORACLE()
}
......
......@@ -18,19 +18,21 @@
"build:go-ffi": "(cd scripts/go-ffi && go build)",
"autogen:invariant-docs": "npx tsx scripts/invariant-doc-gen.ts",
"test": "pnpm build:go-ffi && forge test",
"genesis": "./scripts/generate-l2-genesis.sh",
"coverage": "pnpm build:go-ffi && forge coverage",
"coverage:lcov": "pnpm build:go-ffi && forge coverage --report lcov",
"deploy": "./scripts/deploy.sh",
"gas-snapshot:no-build": "forge snapshot --match-contract GasBenchMark",
"gas-snapshot": "pnpm build:go-ffi && pnpm gas-snapshot:no-build",
"storage-snapshot": "./scripts/storage-snapshot.sh",
"abi-snapshot": "npx tsx scripts/generate-snapshots.ts",
"semver-lock": "forge script scripts/SemverLock.s.sol",
"validate-deploy-configs": "./scripts/check-deploy-configs.sh",
"validate-spacers:no-build": "npx tsx scripts/validate-spacers.ts",
"validate-spacers": "pnpm build && pnpm validate-spacers:no-build",
"slither": "./scripts/slither.sh",
"slither:triage": "TRIAGE_MODE=1 ./scripts/slither.sh",
"clean": "rm -rf ./artifacts ./forge-artifacts ./cache ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo ./scripts/go-ffi/go-ffi ./.testdata",
"clean": "rm -rf ./artifacts ./forge-artifacts ./cache ./tsconfig.tsbuildinfo ./tsconfig.build.tsbuildinfo ./scripts/go-ffi/go-ffi ./.testdata ./deployments/hardhat/*",
"preinstall": "npx only-allow pnpm",
"pre-pr:no-build": "pnpm gas-snapshot:no-build && pnpm storage-snapshot && pnpm semver-lock && pnpm autogen:invariant-docs && pnpm lint && pnpm bindings:go",
"pre-pr": "pnpm clean && pnpm build:go-ffi && pnpm build && pnpm pre-pr:no-build",
......@@ -45,9 +47,9 @@
"lint": "pnpm lint:fix && pnpm lint:check"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/eslint-plugin": "^6.13.0",
"@typescript-eslint/parser": "^6.11.0",
"tsx": "^4.3.0",
"tsx": "^4.5.0",
"typescript": "^5.3.2"
}
}
......@@ -30,6 +30,17 @@ struct Artifact {
string userdoc;
}
/// @notice Contains information about a storage slot. Mirrors the layout of the storage
/// slot object in Forge artifacts so that we can deserialize JSON into this struct.
struct StorageSlot {
uint256 astId;
string _contract;
string label;
uint256 offset;
string slot;
string _type;
}
/// @title Deployer
/// @author tynes
/// @notice A contract that can make deploying and interacting with deployments easy.
......@@ -427,6 +438,36 @@ abstract contract Deployer is Script {
return string(res);
}
/// @dev Pulls the `_initialized` storage slot information from the Forge artifacts for a given contract.
function getInitializedSlot(string memory _contractName) internal returns (StorageSlot memory slot_) {
string memory storageLayout = getStorageLayout(_contractName);
string[] memory command = new string[](3);
command[0] = Executables.bash;
command[1] = "-c";
command[2] = string.concat(
Executables.echo,
" '",
storageLayout,
"'",
" | ",
Executables.jq,
" '.storage[] | select(.label == \"_initialized\" and .type == \"t_uint8\")'"
);
bytes memory rawSlot = vm.parseJson(string(vm.ffi(command)));
slot_ = abi.decode(rawSlot, (StorageSlot));
}
/// @dev Returns the value of the internal `_initialized` storage slot for a given contract.
function loadInitializedSlot(string memory _contractName, bool _isProxy) internal returns (uint8 initialized_) {
StorageSlot memory slot = getInitializedSlot(_contractName);
if (_isProxy) {
_contractName = string.concat(_contractName, "Proxy");
}
bytes32 slotVal = vm.load(mustGetAddress(_contractName), bytes32(vm.parseUint(slot.slot)));
initialized_ = uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF);
}
/// @notice Adds a deployment to the temp deployments file
function _writeTemp(string memory _name, address _deployed) internal {
vm.writeJson({ json: stdJson.serialize("", _name, _deployed), path: tempDeploymentsPath });
......
......@@ -16,19 +16,17 @@ TESTDATA_DIR="$CONTRACTS_DIR/.testdata"
OUTFILE_L2="$TESTDATA_DIR/genesis.json"
OUTFILE_ROLLUP="$TESTDATA_DIR/rollup.json"
OUTFILE_ALLOC="$TESTDATA_DIR/alloc.json"
mkdir -p "$TESTDATA_DIR"
if [ ! -f "$DEPLOY_ARTIFACT" ]; then
forge script $CONTRACTS_DIR/scripts/Deploy.s.sol:Deploy 2>&1 /dev/null
forge script $CONTRACTS_DIR/scripts/Deploy.s.sol:Deploy > /dev/null 2>&1
fi
if [ ! -d "$TESTDATA_DIR" ]; then
mkdir -p "$TESTDATA_DIR"
if [ ! -f "$OUTFILE_L2" ]; then
go run $OP_NODE genesis l2 \
--deploy-config "$CONTRACTS_DIR/deploy-config/hardhat.json" \
--l1-deployments "$DEPLOY_ARTIFACT" \
--l1-starting-block "$L1_STARTING_BLOCK_PATH" \
--outfile.l2 "$OUTFILE_L2" \
--outfile.rollup "$OUTFILE_ROLLUP" >/dev/null 2>&1
--outfile.rollup "$OUTFILE_ROLLUP" > /dev/null 2>&1
fi
import fs from 'fs'
import path from 'path'
const outdir = process.argv[2] || path.join(__dirname, '..', 'snapshots')
const forgeArtifactsDir = path.join(__dirname, '..', 'forge-artifacts')
const getAllContracts = (): Array<string> => {
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(path.join(__dirname, '..', 'src'))
// Assumes there is a single contract per file
return paths
.filter((x) => x.endsWith('.sol'))
.map((p: string) => {
const b = path.basename(p)
return `${b}:${b.replace('.sol', '')}`
})
.sort()
}
type AbiSpecStorageLayoutEntry = {
label: string
slot: number
offset: number
type: string
bytes: number
}
const sortKeys = (obj: any) => {
if (typeof obj !== 'object' || obj === null) {
return obj
}
return Object.keys(obj)
.sort()
.reduce(
(acc, key) => {
acc[key] = sortKeys(obj[key])
return acc
},
Array.isArray(obj) ? [] : {}
)
}
const main = async () => {
console.log(`writing abi spec to ${outdir}`)
const storageLayoutDir = path.join(outdir, 'storageLayout')
const abiDir = path.join(outdir, 'abi')
fs.mkdirSync(storageLayoutDir, { recursive: true })
fs.mkdirSync(abiDir, { recursive: true })
const contracts = getAllContracts()
for (const contract of contracts) {
const toks = contract.split(':')
const contractFile = contract.split(':')[0]
const contractName = toks[1]
let artifactFile = path.join(
forgeArtifactsDir,
contractFile,
`${contractName}.json`
)
// NOTE: Read the first version in the directory. We may want to assert that all version's ABIs are identical
if (!fs.existsSync(artifactFile)) {
const filename = fs.readdirSync(path.dirname(artifactFile))[0]
artifactFile = path.join(path.dirname(artifactFile), filename)
}
const data = fs.readFileSync(artifactFile)
const artifact = JSON.parse(data.toString())
// ignore abstract contracts
if (artifact.bytecode.object === '0x') {
console.log(`ignoring interface ${contractName}`)
continue
}
// HACK: This is a hack to ignore libraries. Not robust against changes to solc's internal ast repr
const isContract = artifact.ast.nodes.some((node: any) => {
return (
node.nodeType === 'ContractDefinition' &&
node.name === contractName &&
node.contractKind === 'contract'
)
})
if (!isContract) {
console.log(`ignoring library/interface ${contractName}`)
continue
}
const storageLayout: AbiSpecStorageLayoutEntry[] = []
for (const storageEntry of artifact.storageLayout.storage) {
// convert ast-based type to solidity type
const typ = artifact.storageLayout.types[storageEntry.type]
if (typ === undefined) {
throw new Error(
`undefined type for ${contractName}:${storageEntry.label}`
)
}
storageLayout.push({
label: typ.label,
bytes: typ.numberOfBytes,
offset: storageEntry.offset,
slot: storageEntry.slot,
type: storageEntry.type,
})
}
// Sort snapshots for easier manual inspection
fs.writeFileSync(
`${abiDir}/${contractName}.json`,
JSON.stringify(sortKeys(artifact.abi), null, 2)
)
fs.writeFileSync(
`${storageLayoutDir}/${contractName}.json`,
JSON.stringify(sortKeys(storageLayout), null, 2)
)
}
}
main()
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "address",
"name": "newAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "oldAddress",
"type": "address"
}
],
"name": "AddressSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"inputs": [
{
"internalType": "string",
"name": "_name",
"type": "string"
}
],
"name": "getAddress",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_name",
"type": "string"
},
{
"internalType": "address",
"name": "_address",
"type": "address"
}
],
"name": "setAddress",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
\ No newline at end of file
[
{
"inputs": [
{
"internalType": "address",
"name": "_admin",
"type": "address"
},
{
"internalType": "string",
"name": "_name",
"type": "string"
},
{
"internalType": "string",
"name": "_version",
"type": "string"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ADMIN",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "PROOF_TYPEHASH",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address payable",
"name": "recipient",
"type": "address"
},
{
"internalType": "bytes32",
"name": "nonce",
"type": "bytes32"
}
],
"internalType": "struct Faucet.DripParameters",
"name": "_params",
"type": "tuple"
},
{
"internalType": "bytes32",
"name": "_id",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_proof",
"type": "bytes"
}
],
"name": "verify",
"outputs": [
{
"internalType": "bool",
"name": "valid_",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
[
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "ReceivedETH",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "withdrawer",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "WithdrewERC20",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "withdrawer",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "WithdrewERC721",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "withdrawer",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "WithdrewETH",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "_target",
"type": "address"
},
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "_value",
"type": "uint256"
}
],
"name": "CALL",
"outputs": [
{
"internalType": "bool",
"name": "success_",
"type": "bool"
},
{
"internalType": "bytes",
"name": "data_",
"type": "bytes"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_target",
"type": "address"
},
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
}
],
"name": "DELEGATECALL",
"outputs": [
{
"internalType": "bool",
"name": "success_",
"type": "bool"
},
{
"internalType": "bytes",
"name": "data_",
"type": "bytes"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ERC20",
"name": "_asset",
"type": "address"
},
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
}
],
"name": "withdrawERC20",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ERC20",
"name": "_asset",
"type": "address"
},
{
"internalType": "address",
"name": "_to",
"type": "address"
}
],
"name": "withdrawERC20",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract ERC721",
"name": "_asset",
"type": "address"
},
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_id",
"type": "uint256"
}
],
"name": "withdrawERC721",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
}
],
"name": "withdrawETH",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "_to",
"type": "address"
}
],
"name": "withdrawETH",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]
\ No newline at end of file
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "about",
"type": "address"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "key",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "val",
"type": "bytes"
}
],
"name": "AttestationCreated",
"type": "event"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "about",
"type": "address"
},
{
"internalType": "bytes32",
"name": "key",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "val",
"type": "bytes"
}
],
"internalType": "struct AttestationStation.AttestationData[]",
"name": "_attestations",
"type": "tuple[]"
}
],
"name": "attest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_about",
"type": "address"
},
{
"internalType": "bytes32",
"name": "_key",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_val",
"type": "bytes"
}
],
"name": "attest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "attestations",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
[
{
"inputs": [
{
"internalType": "address",
"name": "_recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "_minWithdrawalAmount",
"type": "uint256"
},
{
"internalType": "enum FeeVault.WithdrawalNetwork",
"name": "_withdrawalNetwork",
"type": "uint8"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "from",
"type": "address"
}
],
"name": "Withdrawal",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": false,
"internalType": "enum FeeVault.WithdrawalNetwork",
"name": "withdrawalNetwork",
"type": "uint8"
}
],
"name": "Withdrawal",
"type": "event"
},
{
"inputs": [],
"name": "MIN_WITHDRAWAL_AMOUNT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "RECIPIENT",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "WITHDRAWAL_NETWORK",
"outputs": [
{
"internalType": "enum FeeVault.WithdrawalNetwork",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalProcessed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]
\ No newline at end of file
[
{
"inputs": [],
"name": "BlockHashNotPresent",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "blockNumber",
"type": "uint256"
},
{
"indexed": true,
"internalType": "Hash",
"name": "blockHash",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "Timestamp",
"name": "childTimestamp",
"type": "uint64"
}
],
"name": "Checkpoint",
"type": "event"
},
{
"inputs": [],
"name": "checkpoint",
"outputs": [
{
"internalType": "uint256",
"name": "blockNumber_",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_blockNumber",
"type": "uint256"
}
],
"name": "load",
"outputs": [
{
"components": [
{
"internalType": "Hash",
"name": "hash",
"type": "bytes32"
},
{
"internalType": "Timestamp",
"name": "childTimestamp",
"type": "uint64"
}
],
"internalType": "struct BlockOracle.BlockInfo",
"name": "blockInfo_",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
[
{
"anonymous": false,
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "uint256",
"name": "threshold",
"type": "uint256"
}
],
"indexed": false,
"internalType": "struct CheckBalanceLow.Params",
"name": "params",
"type": "tuple"
}
],
"name": "_EventToExposeStructInABI__Params",
"type": "event"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_params",
"type": "bytes"
}
],
"name": "check",
"outputs": [
{
"internalType": "bool",
"name": "execute_",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
[
{
"anonymous": false,
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "treasury",
"type": "address"
},
{
"internalType": "uint256",
"name": "threshold",
"type": "uint256"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"indexed": false,
"internalType": "struct CheckGelatoLow.Params",
"name": "params",
"type": "tuple"
}
],
"name": "_EventToExposeStructInABI__Params",
"type": "event"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_params",
"type": "bytes"
}
],
"name": "check",
"outputs": [
{
"internalType": "bool",
"name": "execute_",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
[
{
"inputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "check",
"outputs": [
{
"internalType": "bool",
"name": "execute_",
"type": "bool"
}
],
"stateMutability": "pure",
"type": "function"
}
]
\ No newline at end of file
[
{
"inputs": [
{
"internalType": "address",
"name": "vetoer_",
"type": "address"
},
{
"internalType": "address",
"name": "initiator_",
"type": "address"
},
{
"internalType": "address",
"name": "target_",
"type": "address"
},
{
"internalType": "uint256",
"name": "operatingDelay_",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AlreadyDelayed",
"type": "error"
},
{
"inputs": [],
"name": "ForwardingEarly",
"type": "error"
},
{
"inputs": [],
"name": "TargetUnitialized",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "expected",
"type": "address"
},
{
"internalType": "address",
"name": "actual",
"type": "address"
}
],
"name": "Unauthorized",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "delay",
"type": "uint256"
}
],
"name": "DelayActivated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "callHash",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "Forwarded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "callHash",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "Initiated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "callHash",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "Vetoed",
"type": "event"
},
{
"stateMutability": "nonpayable",
"type": "fallback"
},
{
"inputs": [],
"name": "delay",
"outputs": [
{
"internalType": "uint256",
"name": "delay_",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "initiator",
"outputs": [
{
"internalType": "address",
"name": "initiator_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "callHash",
"type": "bytes32"
}
],
"name": "queuedAt",
"outputs": [
{
"internalType": "uint256",
"name": "queuedAt_",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "target",
"outputs": [
{
"internalType": "address",
"name": "target_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "vetoer",
"outputs": [
{
"internalType": "address",
"name": "vetoer_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]
\ No newline at end of file
[
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldOwner",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldOwner",
"type": "address"
}
],
"name": "WhitelistDisabled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "deployer",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "whitelisted",
"type": "bool"
}
],
"name": "WhitelistStatusChanged",
"type": "event"
},
{
"inputs": [],
"name": "enableArbitraryContractDeployment",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_deployer",
"type": "address"
}
],
"name": "isDeployerAllowed",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_deployer",
"type": "address"
},
{
"internalType": "bool",
"name": "_isWhitelisted",
"type": "bool"
}
],
"name": "setWhitelistedDeployer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "whitelist",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "Hash",
"name": "uuid",
"type": "bytes32"
}
],
"name": "GameAlreadyExists",
"type": "error"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "gameType",
"type": "uint8"
}
],
"name": "NoImplementation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "disputeProxy",
"type": "address"
},
{
"indexed": true,
"internalType": "GameType",
"name": "gameType",
"type": "uint8"
},
{
"indexed": true,
"internalType": "Claim",
"name": "rootClaim",
"type": "bytes32"
}
],
"name": "DisputeGameCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "impl",
"type": "address"
},
{
"indexed": true,
"internalType": "GameType",
"name": "gameType",
"type": "uint8"
}
],
"name": "ImplementationSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint8",
"name": "version",
"type": "uint8"
}
],
"name": "Initialized",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "_gameType",
"type": "uint8"
},
{
"internalType": "Claim",
"name": "_rootClaim",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_extraData",
"type": "bytes"
}
],
"name": "create",
"outputs": [
{
"internalType": "contract IDisputeGame",
"name": "proxy_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_index",
"type": "uint256"
}
],
"name": "gameAtIndex",
"outputs": [
{
"internalType": "GameType",
"name": "gameType_",
"type": "uint8"
},
{
"internalType": "Timestamp",
"name": "timestamp_",
"type": "uint64"
},
{
"internalType": "contract IDisputeGame",
"name": "proxy_",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gameCount",
"outputs": [
{
"internalType": "uint256",
"name": "gameCount_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "",
"type": "uint8"
}
],
"name": "gameImpls",
"outputs": [
{
"internalType": "contract IDisputeGame",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "_gameType",
"type": "uint8"
},
{
"internalType": "Claim",
"name": "_rootClaim",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_extraData",
"type": "bytes"
}
],
"name": "games",
"outputs": [
{
"internalType": "contract IDisputeGame",
"name": "proxy_",
"type": "address"
},
{
"internalType": "Timestamp",
"name": "timestamp_",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "_gameType",
"type": "uint8"
},
{
"internalType": "Claim",
"name": "_rootClaim",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_extraData",
"type": "bytes"
}
],
"name": "getGameUUID",
"outputs": [
{
"internalType": "Hash",
"name": "uuid_",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "GameType",
"name": "_gameType",
"type": "uint8"
},
{
"internalType": "contract IDisputeGame",
"name": "_impl",
"type": "address"
}
],
"name": "setImplementation",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
[
{
"stateMutability": "payable",
"type": "fallback"
},
{
"inputs": [],
"name": "getL1BlockNumber",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
[
{
"inputs": [
{
"internalType": "bytes",
"name": "_message",
"type": "bytes"
}
],
"name": "passMessageToL1",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "sentMessages",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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