Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: filter withdrawals by configured filters #13

Merged
merged 6 commits into from
Dec 12, 2023
Merged
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
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ build-solidity:
forge build; \
popd;

bindings: build-solidity
jq '.abi' contracts/out/L2StandardBridgeBot.sol/L2StandardBridgeBot.json > contracts/out/L2StandardBridgeBot.sol/L2StandardBridgeBot.abi; \
abigen --abi contracts/out/L2StandardBridgeBot.sol/L2StandardBridgeBot.abi --pkg bindings --type L2StandardBridgeBot --out bindings/L2StandardBridgeBot.go

.PHONY: \
bot \
build-go \
Expand Down
796 changes: 796 additions & 0 deletions bindings/L2StandardBridgeBot.go

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions cmd/bot/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ 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) {
processor := core.NewProcessor(log, l1Client, l2Client, cfg)
limit := 1000
maxBlockTime := time.Now().Unix() - cfg.Misc.ProposeTimeWindow
maxBlockTime := time.Now().Unix() - cfg.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)
Expand All @@ -112,7 +112,7 @@ func ProcessUnprovenBotDelegatedWithdrawals(ctx context.Context, log log.Logger,
// Since the unproven withdrawals are sorted by the on-chain order, we can break here because we know
// that the subsequent of the withdrawals are not ready to be proven yet.
return
} else if strings.Contains(err.Error(), "execution reverted") {
} else if strings.Contains(err.Error(), "execution reverted") || strings.Contains(err.Error(), "filtered") {
owen-reorg marked this conversation as resolved.
Show resolved Hide resolved
// Proven transaction reverted, mark it with the failure reason
result := db.Model(&unproven).Update("failure_reason", err.Error())
if result.Error != nil {
Expand All @@ -130,7 +130,7 @@ 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) {
processor := core.NewProcessor(log, l1Client, l2Client, cfg)
limit := 1000
maxBlockTime := time.Now().Unix() - cfg.Misc.ChallengeTimeWindow
maxBlockTime := time.Now().Unix() - cfg.ChallengeTimeWindow

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)
Expand Down Expand Up @@ -212,7 +212,7 @@ func WatchBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.
timer.Reset(time.Second)
}

toBlockNumber := new(big.Int).Add(fromBlockNumber, big.NewInt(cfg.Misc.LogFilterBlockRange))
toBlockNumber := new(big.Int).Add(fromBlockNumber, big.NewInt(cfg.L2StandardBridgeBot.LogFilterBlockRange))
finalizedHeader, err := client.GetHeaderByTag(context.Background(), "finalized")
if err != nil {
log.Error("call eth_blockNumber", "error", err)
Expand All @@ -228,7 +228,7 @@ func WatchBotDelegatedWithdrawals(ctx context.Context, log log.Logger, db *gorm.
}

log.Info("Fetching logs from blocks", "fromBlock", fromBlockNumber, "toBlock", toBlockNumber)
logs, err := getLogs(client, fromBlockNumber, toBlockNumber, common.HexToAddress(cfg.Misc.L2StandardBridgeBot), core.WithdrawToEventSig())
logs, err := getLogs(client, fromBlockNumber, toBlockNumber, common.HexToAddress(cfg.L2StandardBridgeBot.ContractAddress), core.WithdrawToEventSig())
if err != nil {
log.Error("eth_getLogs", "error", err)
continue
Expand Down
60 changes: 41 additions & 19 deletions core/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,47 @@ const (
)

type Config struct {
Misc MiscConfig `toml:"misc"`
RPCs config.RPCsConfig `toml:"rpcs"`
DB config.DBConfig `toml:"db"`
L1Contracts config.L1Contracts `toml:"l1-contracts"`
Signer SignerConfig `toml:"signer"`
ProposeTimeWindow int64 `toml:"propose-time-window"`
ChallengeTimeWindow int64 `toml:"challenge-time-window"`

RPCs config.RPCsConfig `toml:"rpcs"`
DB config.DBConfig `toml:"db"`
L1Contracts config.L1Contracts `toml:"l1-contracts"`
L2StandardBridgeBot L2StandardBridgeBotConfig `toml:"l2-standard-bridge-bot"`
TxSigner TxSignerConfig `toml:"tx-signer"`
}

type MiscConfig struct {
L2StandardBridgeBot string `toml:"l2-standard-bridge-bot"`
ProposeTimeWindow int64 `toml:"propose-time-window"`
ChallengeTimeWindow int64 `toml:"challenge-time-window"`
LogFilterBlockRange int64 `toml:"log-filter-block-range"`
}

type SignerConfig struct {
type TxSignerConfig struct {
Privkey string `toml:"privkey"`
GasPrice int64 `toml:"gas-price"`
}

type L2StandardBridgeBotConfig struct {
// ContractAddress is the address of the L2StandardBridgeBot contract.
ContractAddress string `toml:"contract-address"`

// LogFilterBlockRange is the number of blocks to filter for events
LogFilterBlockRange int64 `toml:"log-filter-block-range"`

// WhitelistL2TokenList is the list of L2 tokens to whitelist for the L2StandardBridgeBot contract.
//
// L2StandardBridgeBot contract doesn't limit the L2 tokens to be bridged, but this off-chain bot
// process only whitelists the tokens in this list and ignore tokens not in this list.
//
// **IMPORTANT: If this list is unset, all L2 tokens are whitelisted.**
WhitelistL2TokenList *[]string `toml:"whitelist-l2-token-list"`

// UpperMinGasLimit is the upper limit of the minimum gas limit of the L2StandardBridgeBot contract.
//
// **IMPORTANT: If this value is unset, the L2StandardBridgeBot contract doesn't limit the minimum gas limit.**
UpperMinGasLimit *uint32 `toml:"upper-min-gas-limit"`

// UpperExtraDataSize is the upper limit of the extra data size of the L2StandardBridgeBot contract.
//
// **IMPORTANT: If this value is unset, the L2StandardBridgeBot contract doesn't limit the extra data size.**
UpperExtraDataSize *uint32 `toml:"upper-extra-data-size"`
}

// LoadConfig loads the `bot.toml` config file from a given path
func LoadConfig(log log.Logger, path string) (Config, error) {
log.Info("loading config", "path", path)
Expand All @@ -56,21 +78,21 @@ func LoadConfig(log log.Logger, path string) (Config, error) {
return conf, err
}

if conf.Misc.LogFilterBlockRange == 0 {
if conf.L2StandardBridgeBot.LogFilterBlockRange == 0 {
log.Info("setting default log filter block range", "log-filter-block-range", defaultLogFilterBlockRange)
conf.Misc.LogFilterBlockRange = defaultLogFilterBlockRange
conf.L2StandardBridgeBot.LogFilterBlockRange = defaultLogFilterBlockRange
}
if conf.Misc.ProposeTimeWindow == 0 {
if conf.ProposeTimeWindow == 0 {
return conf, errors.New("propose-time-window must be set")
}
if conf.Misc.ChallengeTimeWindow == 0 {
if conf.ChallengeTimeWindow == 0 {
return conf, errors.New("challenge-time-window must be set")
}

if _, _, err = conf.SignerKeyPair(); err != nil {
return conf, err
}
if conf.Signer.GasPrice == 0 {
if conf.TxSigner.GasPrice == 0 {
return conf, errors.New("gas-price must be set")
}

Expand All @@ -79,7 +101,7 @@ func LoadConfig(log log.Logger, path string) (Config, error) {
}

func (c *Config) SignerKeyPair() (*ecdsa.PrivateKey, *common.Address, error) {
privkey, err := crypto.HexToECDSA(c.Signer.Privkey)
privkey, err := crypto.HexToECDSA(c.TxSigner.Privkey)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse privkey: %w", err)
}
Expand Down
96 changes: 90 additions & 6 deletions core/processor.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package core

import (
bindings2 "bnbchain/opbnb-bridge-bot/bindings"
"context"
"errors"
"fmt"
"math/big"

"github.com/ethereum-optimism/optimism/indexer/config"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi"
Expand All @@ -12,7 +15,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"math/big"
)

type Processor struct {
Expand All @@ -23,6 +25,8 @@ type Processor struct {

cfg Config
L2Contracts config.L2Contracts

whitelistL2TokenMap map[common.Address]struct{}
}

func NewProcessor(
Expand All @@ -31,10 +35,17 @@ func NewProcessor(
l2Client *ClientExt,
cfg Config,
) *Processor {
log = log.New("processor", "Processor")

l2Contracts := config.L2ContractsFromPredeploys()
return &Processor{log, l1Client, l2Client, cfg, l2Contracts}

var whitelistL2TokenMap map[common.Address]struct{} = nil
if cfg.L2StandardBridgeBot.WhitelistL2TokenList != nil {
whitelistL2TokenMap = make(map[common.Address]struct{})
for _, l2Token := range *cfg.L2StandardBridgeBot.WhitelistL2TokenList {
whitelistL2TokenMap[common.HexToAddress(l2Token)] = struct{}{}
}
}

return &Processor{log, l1Client, l2Client, cfg, l2Contracts, whitelistL2TokenMap}
}

func (b *Processor) toWithdrawal(botDelegatedWithdrawToEvent *L2ContractEvent, receipt *types.Receipt) (*bindings.TypesWithdrawalTransaction, error) {
Expand Down Expand Up @@ -77,6 +88,11 @@ func (b *Processor) ProveWithdrawalTransaction(ctx context.Context, botDelegated
return err
}

err = b.CheckByFilterOptions(botDelegatedWithdrawToEvent, receipt)
if err != nil {
return err
}

l2BlockNumber := receipt.BlockNumber
withdrawalTx, err := b.toWithdrawal(botDelegatedWithdrawToEvent, receipt)
if err != nil {
Expand Down Expand Up @@ -137,7 +153,7 @@ func (b *Processor) ProveWithdrawalTransaction(ctx context.Context, botDelegated
return err
}

gasPrice := big.NewInt(b.cfg.Signer.GasPrice)
gasPrice := big.NewInt(b.cfg.TxSigner.GasPrice)
signerPrivkey, signerAddress, err := b.cfg.SignerKeyPair()
if err != nil {
return err
Expand Down Expand Up @@ -175,6 +191,11 @@ func (b *Processor) FinalizeMessage(ctx context.Context, botDelegatedWithdrawToE
return err
}

err = b.CheckByFilterOptions(botDelegatedWithdrawToEvent, receipt)
if err != nil {
return err
}

withdrawalTx, err := b.toWithdrawal(botDelegatedWithdrawToEvent, receipt)
if err != nil {
return fmt.Errorf("toWithdrawal err: %v", err)
Expand All @@ -185,7 +206,7 @@ func (b *Processor) FinalizeMessage(ctx context.Context, botDelegatedWithdrawToE
return err
}

gasPrice := big.NewInt(b.cfg.Signer.GasPrice)
gasPrice := big.NewInt(b.cfg.TxSigner.GasPrice)
signerPrivkey, signerAddress, err := b.cfg.SignerKeyPair()
if err != nil {
return err
Expand Down Expand Up @@ -507,3 +528,66 @@ func (b *Processor) toLowLevelMessage(
}
return &withdrawalTx, nil
}

func (b *Processor) CheckByFilterOptions(botDelegatedWithdrawToEvent *L2ContractEvent, receipt *types.Receipt) error {
L2StandardBridgeBotAbi, _ := bindings2.L2StandardBridgeBotMetaData.GetAbi()
withdrawToEvent := bindings2.L2StandardBridgeBotWithdrawTo{}
indexedArgs := func(arguments abi.Arguments) abi.Arguments {
indexedArgs := abi.Arguments{}
for _, arg := range arguments {
if arg.Indexed {
indexedArgs = append(indexedArgs, arg)
}
}
return indexedArgs
}
err := abi.ParseTopics(&withdrawToEvent, indexedArgs(L2StandardBridgeBotAbi.Events["WithdrawTo"].Inputs), receipt.Logs[botDelegatedWithdrawToEvent.LogIndex].Topics[1:])
if err != nil {
return fmt.Errorf("parse indexed event arguments from log.topics of L2StandardBridgeBotWithdrawTo event, err: %v", err)
}

err = L2StandardBridgeBotAbi.UnpackIntoInterface(&withdrawToEvent, "WithdrawTo", receipt.Logs[botDelegatedWithdrawToEvent.LogIndex].Data)
if err != nil {
return fmt.Errorf("parse non-indexed event arguments from log.data of L2StandardBridgeBotWithdrawTo event, err: %v", err)
}

if !IsL2TokenWhitelisted(b.whitelistL2TokenMap, &withdrawToEvent.L2Token) {
return fmt.Errorf("filtered: token is not whitelisted, l2-token: %s", withdrawToEvent.L2Token)
}
if !IsMinGasLimitValid(b.cfg.L2StandardBridgeBot.UpperMinGasLimit, withdrawToEvent.MinGasLimit) {
return fmt.Errorf("filtered: minGasLimit is too large, minGasLimit: %d", withdrawToEvent.MinGasLimit)
}
if !IsExtraDataValid(b.cfg.L2StandardBridgeBot.UpperMinGasLimit, &withdrawToEvent.ExtraData) {
return fmt.Errorf("filtered: extraData is too large, extraDataSize: %d", len(withdrawToEvent.ExtraData))
}

return nil
}

func IsL2TokenWhitelisted(whitelistL2TokenMap map[common.Address]struct{}, l2Token *common.Address) bool {
// nil means all L2 tokens are whitelisted
if whitelistL2TokenMap == nil {
return true
}

_, exists := whitelistL2TokenMap[*l2Token]
return exists
}

func IsMinGasLimitValid(upperMinGasLimit *uint32, minGasLimit uint32) bool {
// nil means no limit
if upperMinGasLimit == nil {
return true
}

return minGasLimit <= *upperMinGasLimit
}

func IsExtraDataValid(upperExtraDataSize *uint32, extraData *[]byte) bool {
// nil means no limit
if upperExtraDataSize == nil {
return true
}

return len(*extraData) <= int(*upperExtraDataSize)
}
52 changes: 52 additions & 0 deletions core/processor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package core_test

import (
"bnbchain/opbnb-bridge-bot/core"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
)

func TestIsL2TokenWhitelisted(t *testing.T) {
// Test case 1: whitelistL2TokenMap is nil
var whitelistL2TokenMap map[common.Address]struct{} = nil
l2Token := common.HexToAddress("0x1234abcdef")
assert.True(t, core.IsL2TokenWhitelisted(whitelistL2TokenMap, &l2Token))

// Test case 2: l2Token is whitelisted
whitelistL2TokenMap = make(map[common.Address]struct{})
whitelistL2TokenMap[l2Token] = struct{}{}
assert.True(t, core.IsL2TokenWhitelisted(whitelistL2TokenMap, &l2Token))

// Test case 3: l2Token is not whitelisted
whitelistL2TokenMap = make(map[common.Address]struct{})
anotherL2Token := &common.Address{}
assert.False(t, core.IsL2TokenWhitelisted(whitelistL2TokenMap, anotherL2Token))

// Test case 4: checksum formated l2Token is whitelisted
whitelistL2TokenMap = make(map[common.Address]struct{})
whitelistL2TokenMap[l2Token] = struct{}{}
checksumL2Token := common.HexToAddress("0x1234ABCDEF")
assert.True(t, core.IsL2TokenWhitelisted(whitelistL2TokenMap, &checksumL2Token))
}
func TestIsMinGasLimitValid(t *testing.T) {
// Test case 1: upperMinGasLimit is nil
var upperMinGasLimit *uint32 = nil
minGasLimit := uint32(100)
assert.True(t, core.IsMinGasLimitValid(upperMinGasLimit, minGasLimit))

// Test case 2: minGasLimit is less than or equal to upperMinGasLimit
upperMinGasLimit = uint32Ptr(200)
minGasLimit = uint32(200)
assert.True(t, core.IsMinGasLimitValid(upperMinGasLimit, minGasLimit))

// Test case 3: minGasLimit is greater than upperMinGasLimit
upperMinGasLimit = uint32Ptr(200)
minGasLimit = uint32(250)
assert.False(t, core.IsMinGasLimitValid(upperMinGasLimit, minGasLimit))
}

func uint32Ptr(n uint32) *uint32 {
return &n
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/BurntSushi/toml v1.3.2
github.com/ethereum-optimism/optimism v1.2.0
github.com/ethereum/go-ethereum v1.13.4
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.25.7
golang.org/x/crypto v0.14.0
gorm.io/driver/mysql v1.5.2
Expand All @@ -29,6 +30,7 @@ require (
github.com/consensys/gnark-crypto v0.12.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set/v2 v2.1.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/ethereum/c-kzg-4844 v0.3.1 // indirect
Expand All @@ -54,6 +56,7 @@ require (
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.17.0 // indirect
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/common v0.44.0 // indirect
Expand All @@ -76,5 +79,6 @@ require (
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
)
Loading