Skip to content

Commit

Permalink
feat(relayer): add the bridge software to send bridge txs (#16498)
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaodino authored Mar 23, 2024
1 parent 830b83e commit 09d79ac
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/relayer/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ type Bridge interface {
InvocationDelay *big.Int
InvocationExtraDelay *big.Int
}, error)
SendMessage(opts *bind.TransactOpts, _message bridge.IBridgeMessage) (*types.Transaction, error)
SuspendMessages(opts *bind.TransactOpts, _msgHashes [][32]byte, _toSuspend bool) (*types.Transaction, error)
}
252 changes: 252 additions & 0 deletions packages/relayer/bridge/bridge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
package bridge

import (
"context"
"crypto/ecdsa"
"encoding/hex"
"fmt"
"math/big"
"sync"
"time"

"github.com/cyberhorsey/errors"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli/v2"
"golang.org/x/exp/slog"

"github.com/taikoxyz/taiko-mono/packages/relayer"
"github.com/taikoxyz/taiko-mono/packages/relayer/bindings/bridge"
)

type ethClient interface {
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
BlockNumber(ctx context.Context) (uint64, error)
BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
SuggestGasPrice(ctx context.Context) (*big.Int, error)
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
ChainID(ctx context.Context) (*big.Int, error)
}

type Bridge struct {
cancel context.CancelFunc

srcEthClient ethClient
destEthClient ethClient

ecdsaKey *ecdsa.PrivateKey

srcBridge relayer.Bridge
destBridge relayer.Bridge

mu *sync.Mutex

addr common.Address

backOffRetryInterval time.Duration
backOffMaxRetries uint64
ethClientTimeout time.Duration

wg *sync.WaitGroup

srcChainId *big.Int
destChainId *big.Int

bridgeMessageValue *big.Int
}

func (b *Bridge) InitFromCli(ctx context.Context, c *cli.Context) error {
cfg, err := NewConfigFromCliContext(c)
if err != nil {
return err
}

return InitFromConfig(ctx, b, cfg)
}

// nolint: funlen
func InitFromConfig(ctx context.Context, b *Bridge, cfg *Config) error {
srcEthClient, err := ethclient.Dial(cfg.SrcRPCUrl)
if err != nil {
return err
}

destEthClient, err := ethclient.Dial(cfg.DestRPCUrl)
if err != nil {
return err
}

srcBridge, err := bridge.NewBridge(cfg.SrcBridgeAddress, srcEthClient)
if err != nil {
return err
}

destBridge, err := bridge.NewBridge(cfg.DestBridgeAddress, destEthClient)
if err != nil {
return err
}

srcChainID, err := srcEthClient.ChainID(context.Background())
if err != nil {
return err
}

destChainID, err := destEthClient.ChainID(context.Background())
if err != nil {
return err
}

publicKey := cfg.BridgePrivateKey.Public()

publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return errors.New("unable to convert public key")
}

b.srcEthClient = srcEthClient
b.destEthClient = destEthClient

b.destBridge = destBridge
b.srcBridge = srcBridge

b.ecdsaKey = cfg.BridgePrivateKey
b.addr = crypto.PubkeyToAddress(*publicKeyECDSA)

b.srcChainId = srcChainID
b.destChainId = destChainID

b.wg = &sync.WaitGroup{}
b.mu = &sync.Mutex{}

b.backOffRetryInterval = time.Duration(cfg.BackoffRetryInterval) * time.Second
b.backOffMaxRetries = cfg.BackOffMaxRetrys
b.ethClientTimeout = time.Duration(cfg.ETHClientTimeout) * time.Second

b.bridgeMessageValue = cfg.BridgeMessageValue

return nil
}

func (b *Bridge) Name() string {
return "bridge"
}

func (b *Bridge) Close(ctx context.Context) {
b.cancel()

b.wg.Wait()
}

func (b *Bridge) Start() error {
slog.Info("Start bridge")

ctx, cancel := context.WithCancel(context.Background())

b.cancel = cancel

_ = b.submitBridgeTx(ctx)

return nil
}

func (b *Bridge) setLatestNonce(ctx context.Context, auth *bind.TransactOpts) error {
pendingNonce, err := b.srcEthClient.PendingNonceAt(ctx, b.addr)
if err != nil {
return err
}

auth.Nonce = big.NewInt(int64(pendingNonce))

return nil
}

func (b *Bridge) estimateGas(
ctx context.Context, message bridge.IBridgeMessage) (uint64, error) {
auth, err := bind.NewKeyedTransactorWithChainID(b.ecdsaKey, new(big.Int).SetUint64(message.SrcChainId))
if err != nil {
return 0, errors.Wrap(err, "bind.NewKeyedTransactorWithChainID")
}

// estimate gas with auth.NoSend set to true
auth.NoSend = true
auth.Context = ctx
auth.GasLimit = 500000

tx, err := b.srcBridge.SendMessage(auth, message)
if err != nil {
fmt.Println(err)
return 0, errors.Wrap(err, "rcBridge.SendMessage")
}

gasPaddingAmt := uint64(80000)

return tx.Gas() + gasPaddingAmt, nil
}

func (b *Bridge) submitBridgeTx(ctx context.Context) error {
srcChainId, err := b.srcEthClient.ChainID(ctx)
if err != nil {
return errors.Wrap(err, "b.srcEthClient.ChainID")
}

destChainId, err := b.destEthClient.ChainID(ctx)
if err != nil {
return errors.Wrap(err, "b.destEthClient.ChainID")
}

auth, err := bind.NewKeyedTransactorWithChainID(b.ecdsaKey, new(big.Int).SetUint64(srcChainId.Uint64()))
if err != nil {
return errors.Wrap(err, "b.NewKeyedTransactorWithChainID")
}

auth.Context = ctx

err = b.setLatestNonce(ctx, auth)
if err != nil {
return errors.New("b.setLatestNonce")
}

processingFee := big.NewInt(10000)
value := new(big.Int)
value.Add(b.bridgeMessageValue, processingFee)
auth.Value = value

message := bridge.IBridgeMessage{
Id: big.NewInt(0),
From: b.addr,
SrcChainId: srcChainId.Uint64(),
DestChainId: destChainId.Uint64(),
SrcOwner: b.addr,
DestOwner: b.addr,
To: b.addr,
RefundTo: b.addr,
Value: b.bridgeMessageValue,
Fee: processingFee,
GasLimit: big.NewInt(140000),
Data: []byte{},
Memo: "",
}

gas, err := b.estimateGas(ctx, message)
if err != nil || gas == 0 {
slog.Info("gas estimation failed, hardcoding gas limit", "b.estimateGas:", err)
}

auth.GasLimit = gas

tx, err := b.srcBridge.SendMessage(auth, message)
if err != nil {
fmt.Println("b.srcBridge.SendMessage", err)
return errors.Wrap(err, "rcBridge.SendMessage")
}

slog.Info("Sent tx", "txHash", hex.EncodeToString(tx.Hash().Bytes()))

return nil
}
68 changes: 68 additions & 0 deletions packages/relayer/bridge/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package bridge

import (
"crypto/ecdsa"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/taikoxyz/taiko-mono/packages/relayer/cmd/flags"
"github.com/urfave/cli/v2"
)

type Config struct {
// address configs
SrcBridgeAddress common.Address
DestBridgeAddress common.Address

// private key
BridgePrivateKey *ecdsa.PrivateKey

// processing configs
Confirmations uint64
ConfirmationsTimeout uint64
EnableTaikoL2 bool

// backoff configs
BackoffRetryInterval uint64
BackOffMaxRetrys uint64

// rpc configs
SrcRPCUrl string
DestRPCUrl string
ETHClientTimeout uint64

// BridgeMessage
BridgeMessageValue *big.Int
}

// NewConfigFromCliContext creates a new config instance from command line flags.
func NewConfigFromCliContext(c *cli.Context) (*Config, error) {
bridgePrivateKey, err := crypto.ToECDSA(
common.Hex2Bytes(c.String(flags.BridgePrivateKey.Name)),
)
if err != nil {
return nil, fmt.Errorf("invalid bridgePrivateKey: %w", err)
}

bridgeMessageValue, ok := new(big.Int).SetString(c.String(flags.BridgeMessageValue.Name), 10)
if !ok {
return nil, fmt.Errorf("invalid bridgeMessageValue")
}

return &Config{
BridgePrivateKey: bridgePrivateKey,
DestBridgeAddress: common.HexToAddress(c.String(flags.DestBridgeAddress.Name)),
SrcBridgeAddress: common.HexToAddress(c.String(flags.SrcBridgeAddress.Name)),
SrcRPCUrl: c.String(flags.SrcRPCUrl.Name),
DestRPCUrl: c.String(flags.DestRPCUrl.Name),
Confirmations: c.Uint64(flags.Confirmations.Name),
ConfirmationsTimeout: c.Uint64(flags.ConfirmationTimeout.Name),
EnableTaikoL2: c.Bool(flags.EnableTaikoL2.Name),
BackoffRetryInterval: c.Uint64(flags.BackOffRetryInterval.Name),
BackOffMaxRetrys: c.Uint64(flags.BackOffMaxRetrys.Name),
ETHClientTimeout: c.Uint64(flags.ETHClientTimeout.Name),
BridgeMessageValue: bridgeMessageValue,
}, nil
}
30 changes: 30 additions & 0 deletions packages/relayer/cmd/flags/bridge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package flags

import (
"github.com/urfave/cli/v2"
)

var (
BridgePrivateKey = &cli.StringFlag{
Name: "bridgePrivateKey",
Usage: "Private key to send a bridge",
Required: true,
Category: bridegCategory,
EnvVars: []string{"BRIDGE_PRIVATE_KEY"},
}
BridgeMessageValue = &cli.StringFlag{
Name: "bridgeMessageValue",
Usage: "Value in the bridge message",
Required: true,
Category: bridegCategory,
EnvVars: []string{"BRIDGE_MESSAGE_VALUE"},
}
)

var BridgeFlags = MergeFlags(CommonFlags, QueueFlags, []cli.Flag{
BridgePrivateKey,
BridgeMessageValue,
SrcBridgeAddress,
DestBridgeAddress,
SrcTaikoAddress,
})
1 change: 1 addition & 0 deletions packages/relayer/cmd/flags/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var (
indexerCategory = "INDEXER"
processorCategory = "PROCESSOR"
watchdogCategory = "WATCHDOG"
bridegCategory = "BRIDGE"
)

var (
Expand Down
8 changes: 8 additions & 0 deletions packages/relayer/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/joho/godotenv"
"github.com/taikoxyz/taiko-mono/packages/relayer/api"
"github.com/taikoxyz/taiko-mono/packages/relayer/bridge"
"github.com/taikoxyz/taiko-mono/packages/relayer/cmd/flags"
"github.com/taikoxyz/taiko-mono/packages/relayer/cmd/utils"
"github.com/taikoxyz/taiko-mono/packages/relayer/indexer"
Expand Down Expand Up @@ -66,6 +67,13 @@ func main() {
Description: "Taiko relayer watchdog software",
Action: utils.SubcommandAction(new(watchdog.Watchdog)),
},
{
Name: "bridge",
Flags: flags.BridgeFlags,
Usage: "Starts the bridge software",
Description: "Taiko relayer bridge software",
Action: utils.SubcommandAction(new(bridge.Bridge)),
},
}

if err := app.Run(os.Args); err != nil {
Expand Down
4 changes: 4 additions & 0 deletions packages/relayer/pkg/mock/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,7 @@ func (b *Bridge) ProveMessageReceived(
func (b *Bridge) ParseMessageSent(log types.Log) (*bridge.BridgeMessageSent, error) {
return &bridge.BridgeMessageSent{}, nil
}

func (b *Bridge) SendMessage(opts *bind.TransactOpts, _message bridge.IBridgeMessage) (*types.Transaction, error) {
return ProcessMessageTx, nil
}

0 comments on commit 09d79ac

Please sign in to comment.