From 1a7128b472115b697dac2fa00db8d901994f5c19 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 18 Mar 2024 07:26:29 +0800 Subject: [PATCH] feat(prover): add `--prover.minEthBalance` and `--prover.minTaikoTokenBalance` flags (#641) --- cmd/flags/prover.go | 14 ++++++ internal/testutils/helper.go | 2 + prover/config.go | 4 ++ prover/prover.go | 2 + prover/server/api.go | 86 ++++++++++++++++++++++++++++++++---- prover/server/server.go | 6 +++ prover/server/server_test.go | 2 + 7 files changed, 108 insertions(+), 8 deletions(-) diff --git a/cmd/flags/prover.go b/cmd/flags/prover.go index 709929861..7de07ca85 100644 --- a/cmd/flags/prover.go +++ b/cmd/flags/prover.go @@ -53,6 +53,18 @@ var ( Category: proverCategory, Value: false, } + MinEthBalance = &cli.Uint64Flag{ + Name: "prover.minEthBalance", + Usage: "Minimum ETH balance (in wei) a prover wants to keep", + Category: proverCategory, + Value: 0, + } + MinTaikoTokenBalance = &cli.Uint64Flag{ + Name: "prover.minTaikoTokenBalance", + Usage: "Minimum Taiko token balance a prover wants to keep", + Category: proverCategory, + Value: 0, + } // Tier fee related. MinOptimisticTierFee = &cli.Uint64Flag{ Name: "minTierFee.optimistic", @@ -192,6 +204,8 @@ var ProverFlags = MergeFlags(CommonFlags, []cli.Flag{ MinOptimisticTierFee, MinSgxTierFee, MinSgxAndZkVMTierFee, + MinEthBalance, + MinTaikoTokenBalance, StartingBlockID, Dummy, GuardianProver, diff --git a/internal/testutils/helper.go b/internal/testutils/helper.go index c106869b6..4b9691e8c 100644 --- a/internal/testutils/helper.go +++ b/internal/testutils/helper.go @@ -175,6 +175,8 @@ func (s *ClientTestSuite) NewTestProverServer( MinOptimisticTierFee: common.Big1, MinSgxTierFee: common.Big1, MinSgxAndZkVMTierFee: common.Big1, + MinEthBalance: common.Big1, + MinTaikoTokenBalance: common.Big1, MaxExpiry: 24 * time.Hour, TaikoL1Address: common.HexToAddress(os.Getenv("TAIKO_L1_ADDRESS")), AssignmentHookAddress: common.HexToAddress(os.Getenv("ASSIGNMENT_HOOK_ADDRESS")), diff --git a/prover/config.go b/prover/config.go index 5c3dd524f..9bc7110d1 100644 --- a/prover/config.go +++ b/prover/config.go @@ -48,6 +48,8 @@ type Config struct { MinOptimisticTierFee *big.Int MinSgxTierFee *big.Int MinSgxAndZkVMTierFee *big.Int + MinEthBalance *big.Int + MinTaikoTokenBalance *big.Int MaxExpiry time.Duration MaxProposedIn uint64 MaxBlockSlippage uint64 @@ -171,6 +173,8 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { MinOptimisticTierFee: new(big.Int).SetUint64(c.Uint64(flags.MinOptimisticTierFee.Name)), MinSgxTierFee: new(big.Int).SetUint64(c.Uint64(flags.MinSgxTierFee.Name)), MinSgxAndZkVMTierFee: new(big.Int).SetUint64(c.Uint64(flags.MinSgxAndZkVMTierFee.Name)), + MinEthBalance: new(big.Int).SetUint64(c.Uint64(flags.MinEthBalance.Name)), + MinTaikoTokenBalance: new(big.Int).SetUint64(c.Uint64(flags.MinTaikoTokenBalance.Name)), MaxExpiry: c.Duration(flags.MaxExpiry.Name), MaxBlockSlippage: c.Uint64(flags.MaxAcceptableBlockSlippage.Name), MaxProposedIn: c.Uint64(flags.MaxProposedIn.Name), diff --git a/prover/prover.go b/prover/prover.go index 395c21dc5..6fd8af26e 100644 --- a/prover/prover.go +++ b/prover/prover.go @@ -179,6 +179,8 @@ func InitFromConfig(ctx context.Context, p *Prover, cfg *Config) (err error) { ProverPrivateKey: p.cfg.L1ProverPrivKey, MinOptimisticTierFee: p.cfg.MinOptimisticTierFee, MinSgxTierFee: p.cfg.MinSgxTierFee, + MinEthBalance: p.cfg.MinEthBalance, + MinTaikoTokenBalance: p.cfg.MinTaikoTokenBalance, MaxExpiry: p.cfg.MaxExpiry, MaxBlockSlippage: p.cfg.MaxBlockSlippage, TaikoL1Address: p.cfg.TaikoL1Address, diff --git a/prover/server/api.go b/prover/server/api.go index a0bcf57c2..eff236a62 100644 --- a/prover/server/api.go +++ b/prover/server/api.go @@ -1,10 +1,12 @@ package server import ( + "context" "math/big" "net/http" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -14,6 +16,10 @@ import ( "github.com/taikoxyz/taiko-client/pkg/rpc" ) +const ( + rpcTimeout = 1 * time.Minute +) + // @title Taiko Prover Server API // @version 1.0 // @termsOfService http://swagger.io/terms/ @@ -100,34 +106,44 @@ func (s *ProverServer) CreateAssignment(c echo.Context) error { "currentUsedCapacity", len(s.proofSubmissionCh), ) + // 1. Check if the request body is valid. if req.TxListHash == (common.Hash{}) { log.Info("Invalid txList hash") return echo.NewHTTPError(http.StatusUnprocessableEntity, "invalid txList hash") } - if req.FeeToken != (common.Address{}) { return echo.NewHTTPError(http.StatusUnprocessableEntity, "only receive ETH") } - ok, err := rpc.CheckProverBalance( + // 2. Check if the prover has the required minimum on-chain ETH and Taiko token balance. + ok, err := s.checkMinEthAndToken(c.Request().Context()) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err) + } + + if !ok { + return echo.NewHTTPError(http.StatusUnprocessableEntity, "insufficient prover balance") + } + + // 3. Check if the prover's token balance is enough to cover the bonds. + if ok, err = rpc.CheckProverBalance( c.Request().Context(), s.rpc, s.proverAddress, s.assignmentHookAddress, s.livenessBond, - ) - if err != nil { + ); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err) } - if !ok { log.Warn( - "Insufficient prover balance, please get more tokens or wait for verification of the blocks you proved", + "Insufficient prover token balance, please get more tokens or wait for verification of the blocks you proved", "prover", s.proverAddress, ) return echo.NewHTTPError(http.StatusUnprocessableEntity, "insufficient prover balance") } + // 4. Check if the proof fee meets prover's minimum requirement for each tier. for _, tier := range req.TierFees { if tier.Tier == encoding.TierGuardianID { continue @@ -157,6 +173,7 @@ func (s *ProverServer) CreateAssignment(c echo.Context) error { } } + // 5. Check if the expiry is too long. if req.Expiry > uint64(time.Now().Add(s.maxExpiry).Unix()) { log.Warn( "Expiry too long", @@ -167,18 +184,18 @@ func (s *ProverServer) CreateAssignment(c echo.Context) error { return echo.NewHTTPError(http.StatusUnprocessableEntity, "expiry too long") } - // Check if the prover has any capacity now. + // 6. Check if the prover has any capacity now. if s.proofSubmissionCh != nil && len(s.proofSubmissionCh) == cap(s.proofSubmissionCh) { log.Warn("Prover does not have capacity", "capacity", cap(s.proofSubmissionCh)) return echo.NewHTTPError(http.StatusUnprocessableEntity, "prover does not have capacity") } + // 7. Encode and sign the prover assignment payload. l1Head, err := s.rpc.L1.BlockNumber(c.Request().Context()) if err != nil { log.Error("Failed to get L1 block head", "error", err) return echo.NewHTTPError(http.StatusUnprocessableEntity, err) } - encoded, err := encoding.EncodeProverAssignmentPayload( s.protocolConfigs.ChainId, s.taikoL1Address, @@ -200,6 +217,7 @@ func (s *ProverServer) CreateAssignment(c echo.Context) error { return echo.NewHTTPError(http.StatusInternalServerError, err) } + // 8. Return the signed payload. return c.JSON(http.StatusOK, &ProposeBlockResponse{ SignedPayload: signed, Prover: s.proverAddress, @@ -207,3 +225,55 @@ func (s *ProverServer) CreateAssignment(c echo.Context) error { MaxProposedIn: s.maxProposedIn, }) } + +// checkMinEthAndToken checks if the prover has the required minimum on-chain ETH and Taiko token balance. +func (s *ProverServer) checkMinEthAndToken(ctx context.Context) (bool, error) { + ctx, cancel := context.WithTimeout(ctx, rpcTimeout) + defer cancel() + + // 1. Check prover's ETH balance. + ethBalance, err := s.rpc.L1.BalanceAt(ctx, s.proverAddress, nil) + if err != nil { + return false, err + } + + log.Info( + "Prover's ETH balance", + "balance", ethBalance, + "address", s.proverAddress.Hex(), + ) + + if ethBalance.Cmp(s.minEthBalance) <= 0 { + log.Warn( + "Prover does not have required minimum on-chain ETH balance", + "providedProver", s.proverAddress.Hex(), + "ethBalance", ethBalance, + "minEthBalance", s.minEthBalance, + ) + return false, nil + } + + // 2. Check prover's Taiko token balance. + balance, err := s.rpc.TaikoToken.BalanceOf(&bind.CallOpts{Context: ctx}, s.proverAddress) + if err != nil { + return false, err + } + + log.Info( + "Prover's Taiko token balance", + "balance", balance.String(), + "address", s.proverAddress.Hex(), + ) + + if balance.Cmp(s.minTaikoTokenBalance) <= 0 { + log.Warn( + "Prover does not have required on-chain Taiko token balance", + "providedProver", s.proverAddress.Hex(), + "taikoTokenBalance", balance, + "minTaikoTokenBalance", s.minTaikoTokenBalance, + ) + return false, nil + } + + return true, nil +} diff --git a/prover/server/server.go b/prover/server/server.go index 99abcc927..5114633c8 100644 --- a/prover/server/server.go +++ b/prover/server/server.go @@ -37,6 +37,8 @@ type ProverServer struct { minOptimisticTierFee *big.Int minSgxTierFee *big.Int minSgxAndZkVMTierFee *big.Int + minEthBalance *big.Int + minTaikoTokenBalance *big.Int maxExpiry time.Duration maxSlippage uint64 maxProposedIn uint64 @@ -54,6 +56,8 @@ type NewProverServerOpts struct { MinOptimisticTierFee *big.Int MinSgxTierFee *big.Int MinSgxAndZkVMTierFee *big.Int + MinEthBalance *big.Int + MinTaikoTokenBalance *big.Int MaxExpiry time.Duration MaxBlockSlippage uint64 MaxProposedIn uint64 @@ -74,6 +78,8 @@ func New(opts *NewProverServerOpts) (*ProverServer, error) { minOptimisticTierFee: opts.MinOptimisticTierFee, minSgxTierFee: opts.MinSgxTierFee, minSgxAndZkVMTierFee: opts.MinSgxAndZkVMTierFee, + minEthBalance: opts.MinEthBalance, + minTaikoTokenBalance: opts.MinTaikoTokenBalance, maxExpiry: opts.MaxExpiry, maxProposedIn: opts.MaxProposedIn, maxSlippage: opts.MaxBlockSlippage, diff --git a/prover/server/server_test.go b/prover/server/server_test.go index 749607328..c1910136a 100644 --- a/prover/server/server_test.go +++ b/prover/server/server_test.go @@ -51,6 +51,8 @@ func (s *ProverServerTestSuite) SetupTest() { MinOptimisticTierFee: common.Big1, MinSgxTierFee: common.Big1, MinSgxAndZkVMTierFee: common.Big1, + MinEthBalance: common.Big1, + MinTaikoTokenBalance: common.Big1, MaxExpiry: time.Hour, ProofSubmissionCh: make(chan<- proofProducer.ProofRequestBody, 1024), TaikoL1Address: common.HexToAddress(os.Getenv("TAIKO_L1_ADDRESS")),