Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ require (
rsc.io/tmplfunc v0.0.3 // indirect
)

replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101511.1-dev.1.0.20250608235258-6005dd53e1b5
replace github.com/ethereum/go-ethereum => github.com/Quarkchain/op-geth v0.0.0-20250707150543-e235a31d4cc0

//replace github.com/ethereum/go-ethereum => ../op-geth

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Quarkchain/op-geth v0.0.0-20250707150543-e235a31d4cc0 h1:PgkV5nv/vftz+9EIabwCPbDFQrRUHiEGBogqJl54mH0=
github.com/Quarkchain/op-geth v0.0.0-20250707150543-e235a31d4cc0/go.mod h1:SkytozVEPtnUeBlquwl0Qv5JKvrN/Y5aqh+VkQo/EOI=
github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI=
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
Expand Down Expand Up @@ -228,8 +230,6 @@ github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/u
github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v1.101511.1-dev.1.0.20250608235258-6005dd53e1b5 h1:wczwl6+GChQaDe3no+h1TegOO8J1Cyb+L3BdFXDsMhk=
github.com/ethereum-optimism/op-geth v1.101511.1-dev.1.0.20250608235258-6005dd53e1b5/go.mod h1:SkytozVEPtnUeBlquwl0Qv5JKvrN/Y5aqh+VkQo/EOI=
github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250603144016-9c45ca7d4508 h1:A/3QVFt+Aa9ozpPVXxUTLui8honBjSusAaiCVRbafgs=
github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250603144016-9c45ca7d4508/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y=
github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w=
Expand Down
58 changes: 50 additions & 8 deletions op-batcher/batcher/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ import (
)

var (
ErrBatcherNotRunning = errors.New("batcher is not running")
emptyTxData = txData{
ErrBatcherNotRunning = errors.New("batcher is not running")
ErrInboxTransactionFailed = errors.New("inbox transaction failed")
emptyTxData = txData{
frames: []frameData{
{
data: []byte{},
Expand Down Expand Up @@ -70,6 +71,7 @@ func (r txRef) string(txIDStringer func(txID) string) string {
type L1Client interface {
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
}

type L2Client interface {
Expand Down Expand Up @@ -112,6 +114,7 @@ type BatchSubmitter struct {
txpoolState TxPoolState
txpoolBlockedBlob bool

inboxIsEOA atomic.Pointer[bool]
channelMgrMutex sync.Mutex // guards channelMgr and prevCurrentL1
channelMgr *channelManager
prevCurrentL1 eth.L1BlockRef // cached CurrentL1 from the last syncStatus
Expand Down Expand Up @@ -929,6 +932,9 @@ func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txRef
candidate = l.calldataTxCandidate(txdata.CallData())
}

if *candidate.To != l.RollupConfig.BatchInboxAddress {
return fmt.Errorf("candidate.To is not inbox")
}
l.sendTx(txdata, false, candidate, queue, receiptsCh)
return nil
}
Expand All @@ -940,12 +946,39 @@ type TxSender[T any] interface {
// sendTx uses the txmgr queue to send the given transaction candidate after setting its
// gaslimit. It will block if the txmgr queue has reached its MaxPendingTransactions limit.
func (l *BatchSubmitter) sendTx(txdata txData, isCancel bool, candidate *txmgr.TxCandidate, queue TxSender[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) {
floorDataGas, err := core.FloorDataGas(candidate.TxData)
if err != nil {
// We log instead of return an error here because the txmgr will do its own gas estimation.
l.Log.Warn("Failed to calculate floor data gas", "err", err)
} else {
candidate.GasLimit = floorDataGas
var isEOAPointer *bool

Choose a reason for hiding this comment

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

This merged PR #64 hasn't been merged.

if l.RollupConfig.UseInboxContract() {
// RollupConfig.UseInboxContract() being true just means the batcher's transaction status matters,
// but the actual inbox may still be an EOA.
isEOAPointer = l.inboxIsEOA.Load()
if isEOAPointer == nil {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
var code []byte
code, err := l.L1Client.CodeAt(ctx, *candidate.To, nil)
if err != nil {
l.Log.Error("CodeAt failed, assuming code exists", "err", err)
// assume code exist, but don't persist the result
isEOA := false
isEOAPointer = &isEOA
} else {
isEOA := len(code) == 0
isEOAPointer = &isEOA
l.inboxIsEOA.Store(isEOAPointer)
}
}
}

// Set GasLimit as intrinstic gas if the inbox is EOA, otherwise
// Leave GasLimit unset when inbox is contract so that later on `EstimateGas` will be called
if !l.RollupConfig.UseInboxContract() || *isEOAPointer {
floorDataGas, err := core.FloorDataGas(candidate.TxData)
if err != nil {
// We log instead of return an error here because the txmgr will do its own gas estimation.
l.Log.Warn("Failed to calculate floor data gas", "err", err)
} else {
candidate.GasLimit = floorDataGas
}
}

queue.Send(txRef{id: txdata.ID(), isCancel: isCancel, isBlob: txdata.asBlob}, *candidate, receiptsCh)
Expand Down Expand Up @@ -980,6 +1013,11 @@ func (l *BatchSubmitter) handleReceipt(r txmgr.TxReceipt[txRef]) {
if r.Err != nil {
l.recordFailedTx(r.ID.id, r.Err)
} else if r.Receipt != nil {
// check tx status
if l.RollupConfig.UseInboxContract() && r.Receipt.Status == types.ReceiptStatusFailed {
l.recordFailedTx(r.ID.id, ErrInboxTransactionFailed)
return
}
l.recordConfirmedTx(r.ID.id, r.Receipt)
}
// Both r.Err and r.Receipt can be nil, in which case we do nothing.
Expand All @@ -997,6 +1035,10 @@ func (l *BatchSubmitter) recordFailedDARequest(id txID, err error) {
func (l *BatchSubmitter) recordFailedTx(id txID, err error) {
l.channelMgrMutex.Lock()
defer l.channelMgrMutex.Unlock()

if l.RollupConfig.UseInboxContract() {
l.inboxIsEOA.Store(nil)
}
l.Log.Warn("Transaction failed to send", logFields(id, err)...)
l.channelMgr.TxFailed(id)
}
Expand Down
27 changes: 24 additions & 3 deletions op-chain-ops/genesis/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,8 @@ type L2InitializationConfig struct {
UpgradeScheduleDeployConfig
L2CoreDeployConfig
AltDADeployConfig
InboxContractConfig
L1ScalarMultiplierConfig
}

func (d *L2InitializationConfig) Check(log log.Logger) error {
Expand Down Expand Up @@ -890,6 +892,18 @@ type L1DependenciesConfig struct {
ProtocolVersionsProxy common.Address `json:"protocolVersionsProxy"`
}

// InboxContractConfig configures whether inbox contract is enabled.
// If enabled, the batcher tx will be further filtered by tx status.
type InboxContractConfig struct {
UseInboxContract bool `json:"useInboxContract,omitempty"`
}

// L1ScalarMultiplierConfig configures the scalar multipliers for L1 base fee and blob base fee.
type L1ScalarMultiplierConfig struct {
L1BaseFeeScalarMultiplier uint64 `json:"l1BaseFeeScalarMultiplier,omitempty"`
L1BlobBaseFeeScalarMultiplier uint64 `json:"l1BlobBaseFeeScalarMultiplier,omitempty"`
}

// DependencyContext is the contextual configuration needed to verify the L1 dependencies,
// used by DeployConfig.CheckAddresses.
type DependencyContext struct {
Expand Down Expand Up @@ -1045,9 +1059,11 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *eth.BlockRef, l2GenesisBlockHa
}

chainOpConfig := &params.OptimismConfig{
EIP1559Elasticity: d.EIP1559Elasticity,
EIP1559Denominator: d.EIP1559Denominator,
EIP1559DenominatorCanyon: &d.EIP1559DenominatorCanyon,
EIP1559Elasticity: d.EIP1559Elasticity,
EIP1559Denominator: d.EIP1559Denominator,
EIP1559DenominatorCanyon: &d.EIP1559DenominatorCanyon,
L1BaseFeeScalarMultiplier: d.L1BaseFeeScalarMultiplier,
L1BlobBaseFeeScalarMultiplier: d.L1BlobBaseFeeScalarMultiplier,
}

var altDA *rollup.AltDAConfig
Expand All @@ -1062,6 +1078,10 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *eth.BlockRef, l2GenesisBlockHa

l1StartTime := l1StartBlock.Time

var inboxContractConfig *rollup.InboxContractConfig
if d.UseInboxContract {
inboxContractConfig = &rollup.InboxContractConfig{UseInboxContract: true}
}
return &rollup.Config{
Genesis: rollup.Genesis{
L1: eth.BlockID{
Expand Down Expand Up @@ -1097,6 +1117,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *eth.BlockRef, l2GenesisBlockHa
InteropTime: d.InteropTime(l1StartTime),
ProtocolVersionsAddress: d.ProtocolVersionsProxy,
AltDAConfig: altDA,
InboxContractConfig: inboxContractConfig,
ChainOpConfig: chainOpConfig,
}, nil
}
Expand Down
8 changes: 5 additions & 3 deletions op-chain-ops/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,11 @@ func NewL2Genesis(config *DeployConfig, l1StartHeader *eth.BlockRef) (*core.Gene
PragueTime: config.IsthmusTime(l1StartTime),
InteropTime: config.InteropTime(l1StartTime),
Optimism: &params.OptimismConfig{
EIP1559Denominator: eip1559Denom,
EIP1559Elasticity: eip1559Elasticity,
EIP1559DenominatorCanyon: &eip1559DenomCanyon,
EIP1559Denominator: eip1559Denom,
EIP1559Elasticity: eip1559Elasticity,
EIP1559DenominatorCanyon: &eip1559DenomCanyon,
L1BaseFeeScalarMultiplier: config.L1BaseFeeScalarMultiplier,
L1BlobBaseFeeScalarMultiplier: config.L1BlobBaseFeeScalarMultiplier,
},
}

Expand Down
63 changes: 57 additions & 6 deletions op-node/rollup/derive/blob_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ type BlobDataSource struct {
ref eth.L1BlockRef
batcherAddr common.Address
dsCfg DataSourceConfig
fetcher L1TransactionFetcher
fetcher L1Fetcher
blobsFetcher L1BlobsFetcher
log log.Logger
}

// NewBlobDataSource creates a new blob data source.
func NewBlobDataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConfig, fetcher L1TransactionFetcher, blobsFetcher L1BlobsFetcher, ref eth.L1BlockRef, batcherAddr common.Address) DataIter {
func NewBlobDataSource(ctx context.Context, log log.Logger, dsCfg DataSourceConfig, fetcher L1Fetcher, blobsFetcher L1BlobsFetcher, ref eth.L1BlockRef, batcherAddr common.Address) DataIter {
return &BlobDataSource{
ref: ref,
dsCfg: dsCfg,
Expand Down Expand Up @@ -73,6 +73,51 @@ func (ds *BlobDataSource) Next(ctx context.Context) (eth.Data, error) {
return data, nil
}

// getTxSucceedMap returns a map indicating whether tx status is successful if useInboxContract;
// if !useInboxContract, nil map is returned to indicate that no status check is needed.
func getTxSucceedMap(ctx context.Context, useInboxContract bool, fetcher L1Fetcher, hash common.Hash) (txSucceeded map[common.Hash]bool, err error) {
if !useInboxContract {
return
}
_, receipts, err := fetcher.FetchReceipts(ctx, hash)
if err != nil {
return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info and receipts: %w", err))
}
txSucceeded = make(map[common.Hash]bool)
for _, receipt := range receipts {
if receipt.Status == types.ReceiptStatusSuccessful {
txSucceeded[receipt.TxHash] = true
}
}
return
}

// getTxSucceed returns all successful txs
func getTxSucceed(ctx context.Context, useInboxContract bool, fetcher L1Fetcher, hash common.Hash, txs types.Transactions) (successTxs types.Transactions, err error) {
if !useInboxContract {
// if !useInboxContract, all txs are considered successful
return txs, nil
}
_, receipts, err := fetcher.FetchReceipts(ctx, hash)
if err != nil {
return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info and receipts: %w", err))
}

txSucceeded := make(map[common.Hash]bool)
for _, receipt := range receipts {
if receipt.Status == types.ReceiptStatusSuccessful {
txSucceeded[receipt.TxHash] = true
}
}
successTxs = make(types.Transactions, 0)
for _, tx := range txs {
if _, ok := txSucceeded[tx.Hash()]; ok {
successTxs = append(successTxs, tx)
}
}
return successTxs, nil
}

// open fetches and returns the blob or calldata (as appropriate) from all valid batcher
// transactions in the referenced block. Returns an empty (non-nil) array if no batcher
// transactions are found. It returns ResetError if it cannot find the referenced block or a
Expand All @@ -85,8 +130,12 @@ func (ds *BlobDataSource) open(ctx context.Context) ([]blobOrCalldata, error) {
}
return nil, NewTemporaryError(fmt.Errorf("failed to open blob data source: %w", err))
}
txSucceedMap, err := getTxSucceedMap(ctx, ds.dsCfg.useInboxContract, ds.fetcher, ds.ref.Hash)
if err != nil {
return nil, err
}

data, hashes := dataAndHashesFromTxs(txs, &ds.dsCfg, ds.batcherAddr, ds.log)
data, hashes := dataAndHashesFromTxs(txs, &ds.dsCfg, ds.batcherAddr, ds.log, txSucceedMap)

if len(hashes) == 0 {
// there are no blobs to fetch so we can return immediately
Expand Down Expand Up @@ -115,13 +164,15 @@ func (ds *BlobDataSource) open(ctx context.Context) ([]blobOrCalldata, error) {
// dataAndHashesFromTxs extracts calldata and datahashes from the input transactions and returns them. It
// creates a placeholder blobOrCalldata element for each returned blob hash that must be populated
// by fillBlobPointers after blob bodies are retrieved.
func dataAndHashesFromTxs(txs types.Transactions, config *DataSourceConfig, batcherAddr common.Address, logger log.Logger) ([]blobOrCalldata, []eth.IndexedBlobHash) {
func dataAndHashesFromTxs(txs types.Transactions, config *DataSourceConfig, batcherAddr common.Address, logger log.Logger, txSucceedMap map[common.Hash]bool) ([]blobOrCalldata, []eth.IndexedBlobHash) {
data := []blobOrCalldata{}
var hashes []eth.IndexedBlobHash
blobIndex := 0 // index of each blob in the block's blob sidecar
for _, tx := range txs {
// skip any non-batcher transactions
if !isValidBatchTx(tx, config.l1Signer, config.batchInboxAddress, batcherAddr, logger) {
// skip any non-batcher transactions or failed transactions
// blobIndex needs to be incremented for both invalid batch tx and failed tx
// if txSucceedMap is nil, it means no status check is needed.
if (!isValidBatchTx(tx, config.l1Signer, config.batchInboxAddress, batcherAddr, logger)) || (txSucceedMap != nil && !txSucceedMap[tx.Hash()]) {
blobIndex += len(tx.BlobHashes())
continue
}
Expand Down
Loading