Skip to content

Commit

Permalink
improve: reduce retry times of proven/finalized transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
bendanzhentan committed Dec 8, 2023
1 parent cb97793 commit 094a157
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 33 deletions.
63 changes: 33 additions & 30 deletions cmd/bot/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ func RunCommand(ctx *cli.Context) error {
if err != nil {
return fmt.Errorf("failed to migrate l2_scanned_blocks: %w", err)
}
err = db.AutoMigrate(&core.L2ContractEvent{})
err = db.AutoMigrate(&core.BotDelegatedWithdrawal{})
if err != nil {
return fmt.Errorf("failed to migrate l2_contract_events: %w", err)
return fmt.Errorf("failed to migrate withdrawals: %w", err)
}

l2ScannedBlock, err := queryL2ScannedBlock(db)
Expand Down Expand Up @@ -103,14 +103,18 @@ func ProcessBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gor
}

func ProcessUnprovenBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.DB, l1Client *core.ClientExt, l2Client *core.ClientExt, cfg core.Config, tombstone *lru.Cache[uint, time.Time]) {
latestProposedNumber, err := core.L2OutputOracleLatestBlockNumber(cfg.L1Contracts.L2OutputOracleProxy, l1Client)
if err != nil {
log.Error("failed to get latest proposed block number", "error", err)
return
}

processor := core.NewProcessor(log, l1Client, l2Client, cfg)
limit := 1000
maxBlockTime := time.Now().Unix() - cfg.Misc.ProposeTimeWindow

unprovens := make([]core.L2ContractEvent, 0)
result := db.Order("id asc").Where("proven = false AND block_time < ? AND failure_reason IS NULL", maxBlockTime).Limit(limit).Find(&unprovens)
unprovens := make([]core.BotDelegatedWithdrawal, 0)
result := db.Order("id asc").Where("proven_time IS NULL AND initiated_block_number <= ? AND failure_reason IS NULL", latestProposedNumber.Uint64()).Limit(limit).Find(&unprovens)
if result.Error != nil {
log.Error("failed to query l2_contract_events", "error", result.Error)
log.Error("failed to query withdrawals", "error", result.Error)
return
}

Expand All @@ -120,13 +124,14 @@ func ProcessUnprovenBotDelegatedWithdrawals(ctx context.Context, log log.Logger,
continue
}

now := time.Now()
err := processor.ProveWithdrawalTransaction(ctx, &unproven)
if err != nil {
if strings.Contains(err.Error(), "OptimismPortal: withdrawal hash has already been proven") {
// The withdrawal has already proven, mark it
result := db.Model(&unproven).Update("proven", true)
result := db.Model(&unproven).Update("proven_time", now)
if result.Error != nil {
log.Error("failed to update proven l2_contract_events", "error", result.Error)
log.Error("failed to update proven withdrawals", "error", result.Error)
}
} else if strings.Contains(err.Error(), "L2OutputOracle: cannot get output for a block that has not been proposed") {
// Since the unproven withdrawals are sorted by the on-chain order, we can break here because we know
Expand All @@ -136,7 +141,7 @@ func ProcessUnprovenBotDelegatedWithdrawals(ctx context.Context, log log.Logger,
// Proven transaction reverted, mark it with the failure reason
result := db.Model(&unproven).Update("failure_reason", err.Error())
if result.Error != nil {
log.Error("failed to update failure reason of l2_contract_events", "error", result.Error)
log.Error("failed to update failure reason of withdrawals", "error", result.Error)
}
} else {
// non-revert error, stop processing the subsequent withdrawals
Expand All @@ -152,12 +157,13 @@ func ProcessUnprovenBotDelegatedWithdrawals(ctx context.Context, log log.Logger,
func ProcessUnfinalizedBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.DB, l1Client *core.ClientExt, l2Client *core.ClientExt, cfg core.Config, tombstone *lru.Cache[uint, time.Time]) {
processor := core.NewProcessor(log, l1Client, l2Client, cfg)
limit := 1000
maxBlockTime := time.Now().Unix() - cfg.Misc.ChallengeTimeWindow
now := time.Now()
maxProvenTime := now.Add(-time.Duration(cfg.Misc.ChallengeTimeWindow) * time.Second)

unfinalizeds := make([]core.L2ContractEvent, 0)
result := db.Order("block_time asc").Where("proven = true AND finalized = false AND block_time < ? AND failure_reason IS NULL", maxBlockTime).Limit(limit).Find(&unfinalizeds)
unfinalizeds := make([]core.BotDelegatedWithdrawal, 0)
result := db.Order("id asc").Where("finalized_time IS NULL AND proven_time IS NOT NULL AND proven_time < ? AND failure_reason IS NULL", maxProvenTime).Limit(limit).Find(&unfinalizeds)
if result.Error != nil {
log.Error("failed to query l2_contract_events", "error", result.Error)
log.Error("failed to query withdrawals", "error", result.Error)
return
}

Expand All @@ -171,9 +177,9 @@ func ProcessUnfinalizedBotDelegatedWithdrawals(ctx context.Context, log log.Logg
if err != nil {
if strings.Contains(err.Error(), "OptimismPortal: withdrawal has already been finalized") {
// The withdrawal has already finalized, mark it
result := db.Model(&unfinalized).Update("finalized", true)
result := db.Model(&unfinalized).Update("finalized_time", now)
if result.Error != nil {
log.Error("failed to update finalized l2_contract_events", "error", result.Error)
log.Error("failed to update finalized withdrawals", "error", result.Error)
}
} else if strings.Contains(err.Error(), "OptimismPortal: withdrawal has not been proven yet") {
log.Error("detected a unproven withdrawal when send finalized transaction", "withdrawal", unfinalized)
Expand All @@ -185,7 +191,7 @@ func ProcessUnfinalizedBotDelegatedWithdrawals(ctx context.Context, log log.Logg
// Finalized transaction reverted, mark it with the failure reason
result := db.Model(&unfinalized).Update("failure_reason", err.Error())
if result.Error != nil {
log.Error("failed to update failure reason of l2_contract_events", "error", result.Error)
log.Error("failed to update failure reason of withdrawals", "error", result.Error)
}
} else {
// non-revert error, stop processing the subsequent withdrawals
Expand All @@ -207,13 +213,10 @@ func storeLogs(db *gorm.DB, client *core.ClientExt, logs []types.Log) error {
return err
}

event := core.L2ContractEvent{
BlockTime: int64(header.Time),
BlockHash: vLog.BlockHash.Hex(),
ContractAddress: vLog.Address.Hex(),
TransactionHash: vLog.TxHash.Hex(),
LogIndex: int(vLog.Index),
EventSignature: vLog.Topics[0].Hex(),
event := core.BotDelegatedWithdrawal{
TransactionHash: vLog.TxHash.Hex(),
LogIndex: int(vLog.Index),
InitiatedBlockNumber: int64(header.Number.Uint64()),
}

deduped := db.Clauses(
Expand All @@ -229,9 +232,9 @@ func storeLogs(db *gorm.DB, client *core.ClientExt, logs []types.Log) error {
}

// WatchBotDelegatedWithdrawals watches for new bot-delegated withdrawals and stores them in the database.
func WatchBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.DB, client *core.ClientExt, l2ScannedBlock *core.L2ScannedBlock, cfg core.Config) {
func WatchBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.DB, client *core.ClientExt, l2StartingBlock *core.L2ScannedBlock, cfg core.Config) {
timer := time.NewTimer(0)
fromBlockNumber := big.NewInt(l2ScannedBlock.Number)
fromBlockNumber := big.NewInt(l2StartingBlock.Number)

for {
select {
Expand Down Expand Up @@ -275,8 +278,8 @@ func WatchBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.
}
}

l2ScannedBlock.Number = toBlockNumber.Int64()
result := db.Where("number >= 0").Updates(l2ScannedBlock)
l2StartingBlock.Number = toBlockNumber.Int64()
result := db.Where("number >= 0").Updates(l2StartingBlock)
if result.Error != nil {
log.Error("update l2_scanned_blocks", "error", result.Error)
}
Expand Down Expand Up @@ -339,14 +342,14 @@ func queryL2ScannedBlock(db *gorm.DB) (*core.L2ScannedBlock, error) {
}

// hasWithdrawalRecentlyProcessed checks if the withdrawal has been processed recently.
func hasWithdrawalRecentlyProcessed(botDelegatedWithdrawal *core.L2ContractEvent, tombstone *lru.Cache[uint, time.Time]) bool {
func hasWithdrawalRecentlyProcessed(botDelegatedWithdrawal *core.BotDelegatedWithdrawal, tombstone *lru.Cache[uint, time.Time]) bool {
const tombstoneTTL = 10 * time.Minute
timestamp, ok := tombstone.Get(botDelegatedWithdrawal.ID)
return ok && timestamp.After(time.Now().Add(-tombstoneTTL))
}

// markWithdrawalAsProcessed marks the withdrawal as processed.
func markWithdrawalAsProcessed(botDelegatedWithdrawal *core.L2ContractEvent, tombstone *lru.Cache[uint, time.Time]) {
func markWithdrawalAsProcessed(botDelegatedWithdrawal *core.BotDelegatedWithdrawal, tombstone *lru.Cache[uint, time.Time]) {
tombstone.Add(botDelegatedWithdrawal.ID, time.Now())
}

Expand Down
13 changes: 13 additions & 0 deletions core/bindings.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package core

import (
"math/big"

"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/common"
"golang.org/x/crypto/sha3"
)
Expand All @@ -17,3 +20,13 @@ func WithdrawToEventSig() common.Hash {
eventSignatureHash := keccak256.Sum(nil)
return common.BytesToHash(eventSignatureHash)
}

// L2OutputOracleLatestBlockNumber calls the "latestBlockNumber" function on the L2OutputOracle contract at the given address.
func L2OutputOracleLatestBlockNumber(address common.Address, l1Client *ClientExt) (*big.Int, error) {
caller, err := bindings.NewL2OutputOracleCaller(address, l1Client)
if err != nil {
return nil, err
}

return caller.LatestBlockNumber(nil)
}
6 changes: 3 additions & 3 deletions core/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func NewProcessor(
return &Processor{log, l1Client, l2Client, cfg, l2Contracts}
}

func (b *Processor) toWithdrawal(botDelegatedWithdrawToEvent *L2ContractEvent, receipt *types.Receipt) (*bindings.TypesWithdrawalTransaction, error) {
func (b *Processor) toWithdrawal(botDelegatedWithdrawToEvent *BotDelegatedWithdrawal, receipt *types.Receipt) (*bindings.TypesWithdrawalTransaction, error) {
// Events flow:
//
// event[i-5]: WithdrawalInitiated
Expand Down Expand Up @@ -71,7 +71,7 @@ func (b *Processor) toWithdrawal(botDelegatedWithdrawToEvent *L2ContractEvent, r
return withdrawalTx, nil
}

func (b *Processor) ProveWithdrawalTransaction(ctx context.Context, botDelegatedWithdrawToEvent *L2ContractEvent) error {
func (b *Processor) ProveWithdrawalTransaction(ctx context.Context, botDelegatedWithdrawToEvent *BotDelegatedWithdrawal) error {
receipt, err := b.L2Client.TransactionReceipt(ctx, common.HexToHash(botDelegatedWithdrawToEvent.TransactionHash))
if err != nil {
return err
Expand Down Expand Up @@ -169,7 +169,7 @@ func (b *Processor) ProveWithdrawalTransaction(ctx context.Context, botDelegated
}

// FinalizeMessage https://github.com/ethereum-optimism/optimism/blob/d90e7818de894f0bc93ae7b449b9049416bda370/packages/sdk/src/cross-chain-messenger.ts#L1611
func (b *Processor) FinalizeMessage(ctx context.Context, botDelegatedWithdrawToEvent *L2ContractEvent) error {
func (b *Processor) FinalizeMessage(ctx context.Context, botDelegatedWithdrawToEvent *BotDelegatedWithdrawal) error {
receipt, err := b.L2Client.TransactionReceipt(ctx, common.HexToHash(botDelegatedWithdrawToEvent.TransactionHash))
if err != nil {
return err
Expand Down

0 comments on commit 094a157

Please sign in to comment.