diff --git a/packages/relayer/api/api.go b/packages/relayer/api/api.go index 79cd21b7031..ae83627f13c 100644 --- a/packages/relayer/api/api.go +++ b/packages/relayer/api/api.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/labstack/echo/v4" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/taikol2" "github.com/taikoxyz/taiko-mono/packages/relayer/pkg/http" "github.com/taikoxyz/taiko-mono/packages/relayer/pkg/repo" "github.com/urfave/cli/v2" @@ -55,12 +56,19 @@ func InitFromConfig(ctx context.Context, api *API, cfg *Config) (err error) { return err } + taikoL2, err := taikol2.NewTaikoL2(cfg.DestTaikoAddress, destEthClient) + if err != nil { + return err + } + srv, err := http.NewServer(http.NewServerOpts{ - EventRepo: eventRepository, - Echo: echo.New(), - CorsOrigins: cfg.CORSOrigins, - SrcEthClient: srcEthClient, - DestEthClient: destEthClient, + EventRepo: eventRepository, + Echo: echo.New(), + CorsOrigins: cfg.CORSOrigins, + SrcEthClient: srcEthClient, + DestEthClient: destEthClient, + TaikoL2: taikoL2, + ProcessingFeeMultiplier: cfg.ProcessingFeeMultiplier, }) if err != nil { return err diff --git a/packages/relayer/api/config.go b/packages/relayer/api/config.go index 5cd75cb4341..9c25d25e5a5 100644 --- a/packages/relayer/api/config.go +++ b/packages/relayer/api/config.go @@ -3,6 +3,7 @@ package api import ( "strings" + "github.com/ethereum/go-ethereum/common" "github.com/taikoxyz/taiko-mono/packages/relayer/cmd/flags" "github.com/taikoxyz/taiko-mono/packages/relayer/pkg/db" "github.com/urfave/cli/v2" @@ -22,10 +23,12 @@ type Config struct { DatabaseMaxConnLifetime uint64 CORSOrigins []string // rpc configs - SrcRPCUrl string - DestRPCUrl string - HTTPPort uint64 - OpenDBFunc func() (DB, error) + SrcRPCUrl string + DestRPCUrl string + ProcessingFeeMultiplier float64 + DestTaikoAddress common.Address + HTTPPort uint64 + OpenDBFunc func() (DB, error) } // NewConfigFromCliContext creates a new config instance from command line flags. @@ -42,6 +45,8 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { HTTPPort: c.Uint64(flags.HTTPPort.Name), SrcRPCUrl: c.String(flags.SrcRPCUrl.Name), DestRPCUrl: c.String(flags.DestRPCUrl.Name), + ProcessingFeeMultiplier: c.Float64(flags.ProcessingFeeMultiplier.Name), + DestTaikoAddress: common.HexToAddress(c.String(flags.DestTaikoAddress.Name)), OpenDBFunc: func() (DB, error) { return db.OpenDBConnection(db.DBConnectionOpts{ Name: c.String(flags.DatabaseUsername.Name), diff --git a/packages/relayer/api/config_test.go b/packages/relayer/api/config_test.go index a94d134f668..0e07a99668d 100644 --- a/packages/relayer/api/config_test.go +++ b/packages/relayer/api/config_test.go @@ -14,6 +14,7 @@ var ( databaseMaxOpenConns = "10" databaseMaxConnLifetime = "30" HTTPPort = "1000" + destTaikoAddress = "0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377" ) func setupApp() *cli.App { @@ -44,6 +45,7 @@ func TestNewConfigFromCliContext(t *testing.T) { assert.Equal(t, uint64(1000), c.HTTPPort) assert.Equal(t, "srcRpcUrl", c.SrcRPCUrl) assert.Equal(t, "destRpcUrl", c.DestRPCUrl) + assert.Equal(t, destTaikoAddress, c.DestTaikoAddress.Hex()) c.OpenDBFunc = func() (DB, error) { return &mock.DB{}, nil @@ -67,5 +69,6 @@ func TestNewConfigFromCliContext(t *testing.T) { "--" + flags.HTTPPort.Name, HTTPPort, "--" + flags.SrcRPCUrl.Name, "srcRpcUrl", "--" + flags.DestRPCUrl.Name, "destRpcUrl", + "--" + flags.DestTaikoAddress.Name, destTaikoAddress, })) } diff --git a/packages/relayer/cmd/flags/api.go b/packages/relayer/cmd/flags/api.go index acae753973a..a53fac8479e 100644 --- a/packages/relayer/cmd/flags/api.go +++ b/packages/relayer/cmd/flags/api.go @@ -20,10 +20,19 @@ var ( Value: "*", EnvVars: []string{"HTTP_CORS_ORIGINS"}, } + ProcessingFeeMultiplier = &cli.Float64Flag{ + Name: "processingFeeMultiplier", + Usage: "Processing fee multiplier", + Category: indexerCategory, + Value: 2.5, + EnvVars: []string{"PROCESSING_FEE_MULTIPLIER"}, + } ) var APIFlags = MergeFlags(CommonFlags, []cli.Flag{ // optional HTTPPort, CORSOrigins, + ProcessingFeeMultiplier, + DestTaikoAddress, }) diff --git a/packages/relayer/pkg/http/get_recommended_processing_fees.go b/packages/relayer/pkg/http/get_recommended_processing_fees.go new file mode 100644 index 00000000000..389c62f1532 --- /dev/null +++ b/packages/relayer/pkg/http/get_recommended_processing_fees.go @@ -0,0 +1,193 @@ +package http + +import ( + "context" + "math/big" + "net/http" + "time" + + "github.com/cyberhorsey/webutils" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/params" + "github.com/labstack/echo/v4" +) + +type getRecommendedProcessingFeesResponse struct { + Fees []fee `json:"fees"` +} + +type fee struct { + Type string `json:"type"` + Amount string `json:"amount"` + DestChainID uint64 `json:"destChainID"` +} + +type FeeType uint64 + +// gas limits +var ( + Eth FeeType = 900000 + ERC20NotDeployed FeeType = 1650000 + ERC20Deployed FeeType = 1000000 + ERC721NotDeployed FeeType = 2500000 + ERC721Deployed FeeType = 1500000 + ERC1155NotDeployed FeeType = 2650000 + ERC1155Deployed FeeType = 1650000 +) + +var ( + feeTypes = []FeeType{ + Eth, + ERC20Deployed, + ERC20NotDeployed, + ERC721Deployed, + ERC721NotDeployed, + ERC1155Deployed, + ERC1155NotDeployed} +) + +func (f FeeType) String() string { + switch f { + case Eth: + return "eth" + case ERC20NotDeployed: + return "erc20NotDeployed" + case ERC20Deployed: + return "erc20Deployed" + case ERC721Deployed: + return "erc721Deployed" + case ERC721NotDeployed: + return "erc721NotDeployed" + case ERC1155NotDeployed: + return "erc1155NotDeployed" + case ERC1155Deployed: + return "erc1155Deployed" + default: + return "" + } +} + +type layer int + +const ( + Layer1 layer = iota + Layer2 layer = iota +) + +// getBlockInfoResponse +// +// returns block info for the chains +// +// @Summary Get block info +// @ID get-block-info +// @Accept json +// @Produce json +// @Success 200 {object} getBlockInfoResponse +// @Router /blockInfo [get] +func (srv *Server) GetRecommendedProcessingFees(c echo.Context) error { + fees := make([]fee, 0) + + srcChainID, err := srv.srcEthClient.ChainID(c.Request().Context()) + if err != nil { + return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) + } + + destChainID, err := srv.destEthClient.ChainID(c.Request().Context()) + if err != nil { + return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) + } + + srcGasTipCap, err := srv.srcEthClient.SuggestGasTipCap(c.Request().Context()) + if err != nil { + return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) + } + + srcBaseFee, err := srv.getDestChainBaseFee(c.Request().Context(), Layer1, srcChainID) + if err != nil { + return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) + } + + destBaseFee, err := srv.getDestChainBaseFee(c.Request().Context(), Layer2, destChainID) + if err != nil { + return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) + } + + destGasTipCap, err := srv.destEthClient.SuggestGasTipCap(c.Request().Context()) + if err != nil { + return webutils.LogAndRenderErrors(c, http.StatusUnprocessableEntity, err) + } + + for _, f := range feeTypes { + fees = append(fees, fee{ + Type: f.String(), + Amount: srv.getCost(c.Request().Context(), uint64(f), srcGasTipCap, srcBaseFee, Layer1).String(), + DestChainID: srcChainID.Uint64(), + }) + + fees = append(fees, fee{ + Type: f.String(), + Amount: srv.getCost(c.Request().Context(), uint64(f), destGasTipCap, destBaseFee, Layer2).String(), + DestChainID: destChainID.Uint64(), + }) + } + + resp := getRecommendedProcessingFeesResponse{ + Fees: fees, + } + + return c.JSON(http.StatusOK, resp) +} + +func (srv *Server) getCost( + ctx context.Context, + gasLimit uint64, + gasTipCap *big.Int, + baseFee *big.Int, + destLayer layer, +) *big.Int { + cost := new(big.Int).Mul( + new(big.Int).SetUint64(gasLimit), + new(big.Int).Add(gasTipCap, baseFee)) + + if destLayer == Layer2 { + return cost + } + + costRat := new(big.Rat).SetInt(cost) + + multiplierRat := new(big.Rat).SetFloat64(srv.processingFeeMultiplier) + + costRat.Mul(costRat, multiplierRat) + + costAfterMultiplier := new(big.Int).Div(costRat.Num(), costRat.Denom()) + + return costAfterMultiplier +} + +func (srv *Server) getDestChainBaseFee(ctx context.Context, l layer, chainID *big.Int) (*big.Int, error) { + blk, err := srv.destEthClient.BlockByNumber(ctx, nil) + if err != nil { + return nil, err + } + + var baseFee *big.Int + + // if layerL1 bridge, we need to calc basefee on Layer 2, since it's the dest chain + if l == Layer1 { + gasUsed := uint32(blk.GasUsed()) + timeSince := uint64(time.Since(time.Unix(int64(blk.Time()), 0))) + bf, err := srv.taikoL2.GetBasefee(&bind.CallOpts{Context: ctx}, timeSince, gasUsed) + + if err != nil { + return nil, err + } + + baseFee = bf.Basefee + } else { + cfg := params.NetworkIDToChainConfigOrDefault(chainID) + baseFee = eip1559.CalcBaseFee(cfg, blk.Header()) + } + + return baseFee, nil +} diff --git a/packages/relayer/pkg/http/routes.go b/packages/relayer/pkg/http/routes.go index 0bc7f63a8ee..8d556d87bf5 100644 --- a/packages/relayer/pkg/http/routes.go +++ b/packages/relayer/pkg/http/routes.go @@ -7,4 +7,5 @@ func (srv *Server) configureRoutes() { srv.echo.GET("/events", srv.GetEventsByAddress) srv.echo.GET("/blockInfo", srv.GetBlockInfo) srv.echo.GET("/suspendedTransactions", srv.GetSuspendedTransactions) + srv.echo.GET("/recommendedProcessingFees", srv.GetRecommendedProcessingFees) } diff --git a/packages/relayer/pkg/http/server.go b/packages/relayer/pkg/http/server.go index 905449c2386..5ee387b8a00 100644 --- a/packages/relayer/pkg/http/server.go +++ b/packages/relayer/pkg/http/server.go @@ -6,8 +6,10 @@ import ( "net/http" "os" + "github.com/ethereum/go-ethereum/core/types" "github.com/labstack/echo/v4/middleware" "github.com/taikoxyz/taiko-mono/packages/relayer" + "github.com/taikoxyz/taiko-mono/packages/relayer/bindings/taikol2" echo "github.com/labstack/echo/v4" ) @@ -15,6 +17,8 @@ import ( type ethClient interface { BlockNumber(ctx context.Context) (uint64, error) ChainID(ctx context.Context) (*big.Int, error) + SuggestGasTipCap(ctx context.Context) (*big.Int, error) + BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) } // @title Taiko Relayer API @@ -30,20 +34,24 @@ type ethClient interface { // @host relayer.katla.taiko.xyz // Server represents an relayer http server instance. type Server struct { - echo *echo.Echo - eventRepo relayer.EventRepository - suspendedTxRepo relayer.SuspendedTransactionRepository - srcEthClient ethClient - destEthClient ethClient + echo *echo.Echo + eventRepo relayer.EventRepository + suspendedTxRepo relayer.SuspendedTransactionRepository + srcEthClient ethClient + destEthClient ethClient + processingFeeMultiplier float64 + taikoL2 *taikol2.TaikoL2 } type NewServerOpts struct { - Echo *echo.Echo - EventRepo relayer.EventRepository - SuspendedTxRepo relayer.SuspendedTransactionRepository - CorsOrigins []string - SrcEthClient ethClient - DestEthClient ethClient + Echo *echo.Echo + EventRepo relayer.EventRepository + SuspendedTxRepo relayer.SuspendedTransactionRepository + CorsOrigins []string + SrcEthClient ethClient + DestEthClient ethClient + ProcessingFeeMultiplier float64 + TaikoL2 *taikol2.TaikoL2 } func (opts NewServerOpts) Validate() error { @@ -76,11 +84,13 @@ func NewServer(opts NewServerOpts) (*Server, error) { } srv := &Server{ - echo: opts.Echo, - eventRepo: opts.EventRepo, - suspendedTxRepo: opts.SuspendedTxRepo, - srcEthClient: opts.SrcEthClient, - destEthClient: opts.DestEthClient, + echo: opts.Echo, + eventRepo: opts.EventRepo, + suspendedTxRepo: opts.SuspendedTxRepo, + srcEthClient: opts.SrcEthClient, + destEthClient: opts.DestEthClient, + processingFeeMultiplier: opts.ProcessingFeeMultiplier, + taikoL2: opts.TaikoL2, } corsOrigins := opts.CorsOrigins diff --git a/packages/relayer/pkg/proof/encoded_signal_proof.go b/packages/relayer/pkg/proof/encoded_signal_proof.go index d2884c52440..d6c3d3c011e 100644 --- a/packages/relayer/pkg/proof/encoded_signal_proof.go +++ b/packages/relayer/pkg/proof/encoded_signal_proof.go @@ -60,12 +60,6 @@ func (p *Prover) abiEncodeSignalProofWithHops(ctx context.Context, return nil, errors.Wrap(err, "hop p.getEncodedMerkleProof") } - slog.Info("generated hop proof", - "chainID", hop.ChainID.Uint64(), - "blockID", block.NumberU64(), - "rootHash", block.Root(), - ) - hopProofs = append(hopProofs, encoding.HopProof{ BlockID: block.NumberU64(), ChainID: hop.ChainID.Uint64(), @@ -114,10 +108,6 @@ func (p *Prover) getProof( return nil, errors.Wrap(err, "c.CallContext") } - slog.Info("proof generated", - "value", common.Bytes2Hex(ethProof.StorageProof[0].Value), - ) - if new(big.Int).SetBytes(ethProof.StorageProof[0].Value).Int64() == int64(0) { return nil, errors.New("proof will not be valid, expected storageProof to not be 0 but was not") } diff --git a/packages/relayer/processor/process_message.go b/packages/relayer/processor/process_message.go index bdabbe60843..8a66108dfc9 100644 --- a/packages/relayer/processor/process_message.go +++ b/packages/relayer/processor/process_message.go @@ -66,6 +66,8 @@ func (p *Processor) processMessage( return false, errors.Wrap(err, "json.Unmarshal") } + slog.Info("message received", "srcTxHash", msgBody.Event.Raw.TxHash.Hex()) + eventStatus, err := p.eventStatusFromMsgHash(ctx, msgBody.Event.MsgHash) if err != nil { return false, errors.Wrap(err, "p.eventStatusFromMsgHash") @@ -109,12 +111,6 @@ func (p *Processor) processMessage( return false, errors.Wrap(err, "p.destBridge.ProofReceipt") } - slog.Info("proofReceipt", - "receivedAt", proofReceipt.ReceivedAt, - "preferredExecutor", proofReceipt.PreferredExecutor.Hex(), - "msgHash", common.BytesToHash(msgBody.Event.MsgHash[:]).Hex(), - ) - var encodedSignalProof []byte // proof has not been submitted, we need to generate it @@ -123,10 +119,6 @@ func (p *Processor) processMessage( if err != nil { return false, err } - - slog.Info("proof generated", - "msgHash", common.BytesToHash(msgBody.Event.MsgHash[:]).Hex(), - ) } else { // proof has been submitted // we need to check the invocation delay and @@ -242,11 +234,13 @@ func (p *Processor) waitForInvocationDelay( // if its passed already, we can submit return nil } - // its unprocessable, we shouldnt send the transaction. - // wait until it's processable. - t := time.NewTicker(60 * time.Second) - defer t.Stop() + slog.Info("waiting for invocation delay", + "delay", delay.String(), + "processableAt", processableAt.String(), + "now", time.Now().UTC().Unix(), + "difference", new(big.Int).Sub(processableAt, new(big.Int).SetInt64(time.Now().UTC().Unix())), + ) w := time.After(time.Duration(delay.Int64()) * time.Second) @@ -254,11 +248,6 @@ func (p *Processor) waitForInvocationDelay( select { case <-ctx.Done(): return ctx.Err() - case <-t.C: - slog.Info("waiting for invocation delay", - "processableAt", processableAt.String(), - "now", time.Now().UTC().Unix(), - ) case <-w: slog.Info("done waiting for invocation delay") return nil @@ -731,10 +720,21 @@ func (p *Processor) getCost(ctx context.Context, gas uint64, gasTipCap *big.Int, baseFee = eip1559.CalcBaseFee(cfg, blk.Header()) } + slog.Info("cost estimation", + "gas", gas, + "gasTipCap", gasTipCap.String(), + "baseFee", baseFee.String(), + ) + return new(big.Int).Mul( new(big.Int).SetUint64(gas), new(big.Int).Add(gasTipCap, baseFee)), nil } else { + slog.Info("cost estimation", + "gas", gas, + "gasPrice", gasPrice.String(), + ) + return new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas)), nil } }