From 834c0ea62353a5a92245ac5412b7d8714d92c4da Mon Sep 17 00:00:00 2001 From: David Date: Mon, 25 Sep 2023 01:13:44 +0800 Subject: [PATCH] feat(prover_selector): check prover's token balance (#406) --- pkg/rpc/utils.go | 47 +++++++++++++++++++ .../prover_selector/eth_fee_eoa_selector.go | 33 +------------ .../eth_fee_eoa_selector_test.go | 7 --- prover/prover.go | 4 ++ prover/server/api.go | 17 +++++++ prover/server/server.go | 13 +++++ prover/server/server_test.go | 19 ++++++++ testutils/helper.go | 8 ++++ 8 files changed, 109 insertions(+), 39 deletions(-) diff --git a/pkg/rpc/utils.go b/pkg/rpc/utils.go index e416210f7..664254c37 100644 --- a/pkg/rpc/utils.go +++ b/pkg/rpc/utils.go @@ -36,6 +36,53 @@ func GetProtocolStateVariables( return &stateVars, nil } +// CheckProverBalance checks if the prover has the necessary balance either in TaikoL1 token balances +// or, if not, then check allowance, as contract will attempt to burn directly after +// if it doesnt have the available token balance in-contract. +func CheckProverBalance( + ctx context.Context, + rpc *Client, + prover common.Address, + taikoL1Address common.Address, + bond *big.Int, +) (bool, error) { + ctxWithTimeout, cancel := ctxWithTimeoutOrDefault(ctx, defaultTimeout) + defer cancel() + + depositedBalance, err := rpc.TaikoL1.GetTaikoTokenBalance(&bind.CallOpts{Context: ctxWithTimeout}, prover) + if err != nil { + return false, err + } + + if bond.Cmp(depositedBalance) > 0 { + // Check allowance on taiko token contract + allowance, err := rpc.TaikoToken.Allowance(&bind.CallOpts{Context: ctxWithTimeout}, prover, taikoL1Address) + if err != nil { + return false, err + } + + // Check prover's taiko token balance + balance, err := rpc.TaikoToken.BalanceOf(&bind.CallOpts{Context: ctxWithTimeout}, prover) + if err != nil { + return false, err + } + + if bond.Cmp(allowance) > 0 || bond.Cmp(balance) > 0 { + log.Info( + "Assigned prover does not have required on-chain token balance or allowance", + "providedProver", prover.Hex(), + "depositedBalance", depositedBalance.String(), + "taikoTokenBalance", balance, + "allowance", allowance.String(), + "proofBond", bond, + ) + return false, nil + } + } + + return true, nil +} + // WaitReceipt keeps waiting until the given transaction has an execution // receipt to know whether it was reverted or not. func WaitReceipt( diff --git a/proposer/prover_selector/eth_fee_eoa_selector.go b/proposer/prover_selector/eth_fee_eoa_selector.go index 9ce77ca21..c340e9d68 100644 --- a/proposer/prover_selector/eth_fee_eoa_selector.go +++ b/proposer/prover_selector/eth_fee_eoa_selector.go @@ -124,7 +124,7 @@ func (s *ETHFeeEOASelector) AssignProver( } if proverAddress != encoding.OracleProverAddress { - ok, err := s.checkProverBalance(ctx, proverAddress) + ok, err := rpc.CheckProverBalance(ctx, s.rpc, proverAddress, s.taikoL1Address, s.protocolConfigs.ProofBond) if err != nil { log.Warn("Failed to check prover balance", "endpoint", endpoint, "error", err) continue @@ -141,37 +141,6 @@ func (s *ETHFeeEOASelector) AssignProver( return nil, nil, errUnableToFindProver } -// checkProverBalance checks if the prover has the necessary balance either in TaikoL1 token balances -// or, if not, then check allowance, as contract will attempt to burn directly after -// if it doesnt have the available token balance in-contract. -func (s *ETHFeeEOASelector) checkProverBalance(ctx context.Context, prover common.Address) (bool, error) { - taikoTokenBalance, err := s.rpc.TaikoL1.GetTaikoTokenBalance(&bind.CallOpts{Context: ctx}, prover) - if err != nil { - return false, err - } - - if s.protocolConfigs.ProofBond.Cmp(taikoTokenBalance) > 0 { - // Check allowance on taiko token contract - allowance, err := s.rpc.TaikoToken.Allowance(&bind.CallOpts{Context: ctx}, prover, s.taikoL1Address) - if err != nil { - return false, err - } - - if s.protocolConfigs.ProofBond.Cmp(allowance) > 0 { - log.Info( - "Assigned prover does not have required on-chain token balance or allowance", - "providedProver", prover.Hex(), - "taikoTokenBalance", taikoTokenBalance.String(), - "allowance", allowance.String(), - "proofBond", s.protocolConfigs.ProofBond, - ) - return false, nil - } - } - - return true, nil -} - // shuffleProverEndpoints shuffles the current selector's prover endpoints. func (s *ETHFeeEOASelector) shuffleProverEndpoints() []*url.URL { rand.Shuffle(len(s.proverEndpoints), func(i, j int) { diff --git a/proposer/prover_selector/eth_fee_eoa_selector_test.go b/proposer/prover_selector/eth_fee_eoa_selector_test.go index 634aebb96..00fd11dbc 100644 --- a/proposer/prover_selector/eth_fee_eoa_selector_test.go +++ b/proposer/prover_selector/eth_fee_eoa_selector_test.go @@ -1,7 +1,6 @@ package selector import ( - "context" "net/url" "os" "testing" @@ -43,12 +42,6 @@ func (s *ProverSelectorTestSuite) SetupTest() { s.Nil(err) } -func (s *ProverSelectorTestSuite) TestCheckProverBalance() { - ok, err := s.s.checkProverBalance(context.Background(), s.proverAddress) - s.Nil(err) - s.True(ok) -} - func TestProverSelectorTestSuite(t *testing.T) { suite.Run(t, new(ProverSelectorTestSuite)) } diff --git a/prover/prover.go b/prover/prover.go index 3680c37a2..b70741003 100644 --- a/prover/prover.go +++ b/prover/prover.go @@ -210,6 +210,10 @@ func InitFromConfig(ctx context.Context, p *Prover, cfg *Config) (err error) { MinProofFee: p.cfg.MinProofFee, MaxExpiry: p.cfg.MaxExpiry, CapacityManager: p.capacityManager, + TaikoL1Address: p.cfg.TaikoL1Address, + Rpc: p.rpc, + Bond: protocolConfigs.ProofBond, + IsOracle: p.cfg.OracleProver, } if p.cfg.OracleProver { proverServerOpts.ProverPrivateKey = p.cfg.OracleProverPrivateKey diff --git a/prover/server/api.go b/prover/server/api.go index e0039423a..8045494ab 100644 --- a/prover/server/api.go +++ b/prover/server/api.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/labstack/echo/v4" "github.com/taikoxyz/taiko-client/bindings/encoding" + "github.com/taikoxyz/taiko-client/pkg/rpc" ) // Status represents the current prover server status. @@ -50,6 +51,7 @@ type ProposeBlockResponse struct { // @Accept json // @Produce json // @Success 200 {object} ProposeBlockResponse +// @Failure 422 {string} string "insufficient prover balance" // @Failure 422 {string} string "proof fee too low" // @Failure 422 {string} string "expiry too long" // @Failure 422 {string} string "prover does not have capacity" @@ -62,6 +64,21 @@ func (srv *ProverServer) CreateAssignment(c echo.Context) error { log.Info("Propose block data", "fee", req.Fee, "expiry", req.Expiry) + if !srv.isOracle { + ok, err := rpc.CheckProverBalance(c.Request().Context(), srv.rpc, srv.proverAddress, srv.taikoL1Address, srv.bond) + if 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", + "prover", srv.proverAddress, + ) + return echo.NewHTTPError(http.StatusUnprocessableEntity, "insufficient prover balance") + } + } + if req.Fee.Cmp(srv.minProofFee) < 0 { return echo.NewHTTPError(http.StatusUnprocessableEntity, "proof fee too low") } diff --git a/prover/server/server.go b/prover/server/server.go index 78b4add34..b6de95392 100644 --- a/prover/server/server.go +++ b/prover/server/server.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" echo "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" + "github.com/taikoxyz/taiko-client/pkg/rpc" capacity "github.com/taikoxyz/taiko-client/prover/capacity_manager" ) @@ -35,6 +36,10 @@ type ProverServer struct { minProofFee *big.Int maxExpiry time.Duration capacityManager *capacity.CapacityManager + taikoL1Address common.Address + rpc *rpc.Client + bond *big.Int + isOracle bool } // NewProverServerOpts contains all configurations for creating a prover server instance. @@ -43,6 +48,10 @@ type NewProverServerOpts struct { MinProofFee *big.Int MaxExpiry time.Duration CapacityManager *capacity.CapacityManager + TaikoL1Address common.Address + Rpc *rpc.Client + Bond *big.Int + IsOracle bool } // New creates a new prover server instance. @@ -54,6 +63,10 @@ func New(opts *NewProverServerOpts) (*ProverServer, error) { minProofFee: opts.MinProofFee, maxExpiry: opts.MaxExpiry, capacityManager: opts.CapacityManager, + taikoL1Address: opts.TaikoL1Address, + rpc: opts.Rpc, + bond: opts.Bond, + isOracle: opts.IsOracle, } srv.echo.HideBanner = true diff --git a/prover/server/server_test.go b/prover/server/server_test.go index 263bdc6cc..e8b5582e6 100644 --- a/prover/server/server_test.go +++ b/prover/server/server_test.go @@ -18,6 +18,7 @@ import ( echo "github.com/labstack/echo/v4" "github.com/phayes/freeport" "github.com/stretchr/testify/suite" + "github.com/taikoxyz/taiko-client/pkg/rpc" capacity "github.com/taikoxyz/taiko-client/prover/capacity_manager" ) @@ -30,12 +31,30 @@ func (s *ProverServerTestSuite) SetupTest() { l1ProverPrivKey, err := crypto.ToECDSA(common.Hex2Bytes(os.Getenv("L1_PROVER_PRIVATE_KEY"))) s.Nil(err) + timeout := 5 * time.Second + rpcClient, err := rpc.NewClient(context.Background(), &rpc.ClientConfig{ + L1Endpoint: os.Getenv("L1_NODE_WS_ENDPOINT"), + L2Endpoint: os.Getenv("L2_EXECUTION_ENGINE_WS_ENDPOINT"), + TaikoL1Address: common.HexToAddress(os.Getenv("TAIKO_L1_ADDRESS")), + TaikoL2Address: common.HexToAddress(os.Getenv("TAIKO_L2_ADDRESS")), + TaikoTokenAddress: common.HexToAddress(os.Getenv("TAIKO_TOKEN_ADDRESS")), + L2EngineEndpoint: os.Getenv("L2_EXECUTION_ENGINE_AUTH_ENDPOINT"), + JwtSecret: os.Getenv("JWT_SECRET"), + RetryInterval: backoff.DefaultMaxInterval, + Timeout: &timeout, + }) + s.Nil(err) + srv := &ProverServer{ echo: echo.New(), proverPrivateKey: l1ProverPrivKey, minProofFee: common.Big1, maxExpiry: 24 * time.Hour, capacityManager: capacity.New(1024), + taikoL1Address: common.HexToAddress(os.Getenv("TAIKO_L1_ADDRESS")), + rpc: rpcClient, + bond: common.Big0, + isOracle: false, } srv.echo.HideBanner = true diff --git a/testutils/helper.go b/testutils/helper.go index 32552a471..e3ef0b97f 100644 --- a/testutils/helper.go +++ b/testutils/helper.go @@ -9,6 +9,7 @@ import ( "math/big" "net/http" "net/url" + "os" "time" "github.com/cenkalti/backoff/v4" @@ -192,11 +193,18 @@ func NewTestProverServer( capacityManager *capacity.CapacityManager, url *url.URL, ) *server.ProverServer { + protocolConfig, err := s.RpcClient.TaikoL1.GetConfig(nil) + s.Nil(err) + srv, err := server.New(&server.NewProverServerOpts{ ProverPrivateKey: proverPrivKey, MinProofFee: common.Big1, MaxExpiry: 24 * time.Hour, CapacityManager: capacityManager, + TaikoL1Address: common.HexToAddress(os.Getenv("TAIKO_L1_ADDRESS")), + Rpc: s.RpcClient, + Bond: protocolConfig.ProofBond, + IsOracle: true, }) s.Nil(err)