From 4bc2a63c57c3897b2634abc40af72e55522d4af6 Mon Sep 17 00:00:00 2001 From: jeff <113397187+cyberhorsey@users.noreply.github.com> Date: Thu, 30 Nov 2023 19:28:09 -0800 Subject: [PATCH] fix(prover): reorder guardian prover signature && add allowance flag (#457) Co-authored-by: David --- .github/workflows/test.yml | 2 +- cmd/flags/prover.go | 6 +++ prover/config.go | 12 +++++ prover/config_test.go | 4 ++ prover/prover.go | 95 ++++++++++++++++++++++++++++++++++---- prover/prover_test.go | 51 ++++++++++++++++++++ 6 files changed, 161 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1dd970bb5..6e5b3c88a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,7 +45,7 @@ jobs: with: repository: taikoxyz/taiko-mono path: ${{ env.TAIKO_MONO_DIR }} - ref: based_contestable_zkrollup_improved_log + ref: based_contestable_zkrollup_improved - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 diff --git a/cmd/flags/prover.go b/cmd/flags/prover.go index 2de913359..a203ba6e4 100644 --- a/cmd/flags/prover.go +++ b/cmd/flags/prover.go @@ -175,6 +175,11 @@ var ( Value: 16, Category: proverCategory, } + Allowance = &cli.StringFlag{ + Name: "prover.allowance", + Usage: "Amount to approve TaikoL1 contract for TaikoToken usage", + Category: proverCategory, + } ) // All prover flags. @@ -210,4 +215,5 @@ var ProverFlags = MergeFlags(CommonFlags, []cli.Flag{ DatabasePath, DatabaseCacheSize, ProverAssignmentHookAddress, + Allowance, }) diff --git a/prover/config.go b/prover/config.go index ee42fe2ef..15a289675 100644 --- a/prover/config.go +++ b/prover/config.go @@ -52,6 +52,7 @@ type Config struct { MaxBlockSlippage uint64 DatabasePath string DatabaseCacheSize uint64 + Allowance *big.Int } // NewConfigFromCliContext creates a new config instance from command line flags. @@ -105,6 +106,16 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { proveBlockMaxTxGasTipCap = new(big.Int).SetUint64(c.Uint64(flags.ProveBlockMaxTxGasTipCap.Name)) } + var allowance *big.Int = common.Big0 + if c.IsSet(flags.Allowance.Name) { + amt, ok := new(big.Int).SetString(c.String(flags.Allowance.Name), 10) + if !ok { + return nil, fmt.Errorf("error setting allowance config value: %v", c.String(flags.Allowance.Name)) + } + + allowance = amt + } + return &Config{ L1WsEndpoint: c.String(flags.L1WSEndpoint.Name), L1HttpEndpoint: c.String(flags.L1HTTPEndpoint.Name), @@ -144,5 +155,6 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { MaxProposedIn: c.Uint64(flags.MaxProposedIn.Name), DatabasePath: c.String(flags.DatabasePath.Name), DatabaseCacheSize: c.Uint64(flags.DatabaseCacheSize.Name), + Allowance: allowance, }, nil } diff --git a/prover/config_test.go b/prover/config_test.go index 82e75b25e..69b316311 100644 --- a/prover/config_test.go +++ b/prover/config_test.go @@ -18,6 +18,7 @@ var ( l2HttpEndpoint = os.Getenv("L2_EXECUTION_ENGINE_HTTP_ENDPOINT") taikoL1 = os.Getenv("TAIKO_L1_ADDRESS") taikoL2 = os.Getenv("TAIKO_L2_ADDRESS") + allowance = "10000000000000000000000000000000000000000000000000" rpcTimeout = 5 * time.Second minTierFee = 1024 ) @@ -57,6 +58,7 @@ func (s *ProverTestSuite) TestNewConfigFromCliContextGuardianProver() { s.Equal(uint64(128), c.DatabaseCacheSize) s.Equal(uint64(100), c.MaxProposedIn) s.Equal(os.Getenv("ASSIGNMENT_HOOK_ADDRESS"), c.AssignmentHookAddress.String()) + s.Equal(allowance, c.Allowance.String()) return err } @@ -88,6 +90,7 @@ func (s *ProverTestSuite) TestNewConfigFromCliContextGuardianProver() { "--" + flags.DatabasePath.Name, "dbPath", "--" + flags.DatabaseCacheSize.Name, "128", "--" + flags.MaxProposedIn.Name, "100", + "--" + flags.Allowance.Name, allowance, })) } @@ -161,6 +164,7 @@ func (s *ProverTestSuite) SetupApp() *cli.App { &cli.Uint64Flag{Name: flags.DatabaseCacheSize.Name}, &cli.Uint64Flag{Name: flags.MaxProposedIn.Name}, &cli.StringFlag{Name: flags.ProverAssignmentHookAddress.Name}, + &cli.StringFlag{Name: flags.Allowance.Name}, } app.Action = func(ctx *cli.Context) error { _, err := NewConfigFromCliContext(ctx) diff --git a/prover/prover.go b/prover/prover.go index 3fe057a7b..7a69d51dd 100644 --- a/prover/prover.go +++ b/prover/prover.go @@ -257,6 +257,7 @@ func InitFromConfig(ctx context.Context, p *Prover, cfg *Config) (err error) { if p.IsGuardianProver() { proverServerOpts.ProverPrivateKey = p.cfg.GuardianProverPrivateKey } + if p.srv, err = server.New(proverServerOpts); err != nil { return err } @@ -264,10 +265,84 @@ func InitFromConfig(ctx context.Context, p *Prover, cfg *Config) (err error) { return nil } +// setApprovalAmount will set the allowance on the TaikoToken contract for the +// configured proverAddress as owner and the TaikoL1 contract as spender, +// if flag is provided for allowance. +func (p *Prover) setApprovalAmount() error { + if p.cfg.Allowance == nil || p.cfg.Allowance.Cmp(common.Big0) != 1 { + log.Info("skipping setting approval, allowance not set") + return nil + } + + allowance, err := p.rpc.TaikoToken.Allowance( + &bind.CallOpts{}, + p.proverAddress, + p.cfg.TaikoL1Address, + ) + if err != nil { + return err + } + + log.Info("existing allowance for taikoL1 contract", "allowance", allowance.String()) + + if allowance.Cmp(p.cfg.Allowance) == 1 { + log.Info("skipping setting allowance, allowance already greater or equal", + "allowance", allowance.String(), + "approvalAmount", p.cfg.Allowance.String(), + ) + return nil + } + + opts, err := bind.NewKeyedTransactorWithChainID( + p.cfg.L1ProverPrivKey, + p.rpc.L1ChainID, + ) + if err != nil { + return err + } + + log.Info("prover approving taikoL1 for taiko token", "allowance", p.cfg.Allowance.String()) + + tx, err := p.rpc.TaikoToken.Approve( + opts, + p.cfg.TaikoL1Address, + p.cfg.Allowance, + ) + if err != nil { + return err + } + + receipt, err := rpc.WaitReceipt(context.Background(), p.rpc.L1, tx) + if err != nil { + return err + } + + log.Info("prover approved taikoL1 for taiko token", "txHash", receipt.TxHash.Hex()) + + allowance, err = p.rpc.TaikoToken.Allowance( + &bind.CallOpts{}, + p.proverAddress, + p.cfg.TaikoL1Address, + ) + if err != nil { + return err + } + + log.Info("new allowance for taikoL1 contract", "allowance", allowance.String()) + + return nil +} + // Start starts the main loop of the L2 block prover. func (p *Prover) Start() error { + if err := p.setApprovalAmount(); err != nil { + log.Crit("failed to set approval amount", "error", err) + return err + } + p.wg.Add(1) p.initSubscription() + go func() { if err := p.srv.Start(fmt.Sprintf(":%v", p.cfg.HTTPServerPort)); !errors.Is(err, http.ErrServerClosed) { log.Crit("Failed to start http server", "error", err) @@ -391,14 +466,6 @@ func (p *Prover) onBlockProposed( return nil } - // guardian prover must sign each new block and store in database, to be exposed - // via API for liveness checks. - if p.IsGuardianProver() { - if err := p.signBlock(ctx, event.BlockId); err != nil { - return fmt.Errorf("failed to sign block data (eventID %d): %w", event.BlockId, err) - } - } - if _, err := p.rpc.WaitL1Origin(ctx, event.BlockId); err != nil { return fmt.Errorf("failed to wait L1Origin (eventID %d): %w", event.BlockId, err) } @@ -479,6 +546,14 @@ func (p *Prover) onBlockProposed( ) metrics.ProverReceivedProposedBlockGauge.Update(event.BlockId.Int64()) + // guardian prover must sign each new block and store in database, to be exposed + // via API for liveness checks. + if p.IsGuardianProver() { + if err := p.signBlock(ctx, event.BlockId); err != nil { + return fmt.Errorf("failed to sign block data (eventID %d): %w", event.BlockId, err) + } + } + handleBlockProposedEvent := func() error { defer func() { <-p.proposeConcurrencyGuard }() @@ -1185,6 +1260,8 @@ func (p *Prover) signBlock(ctx context.Context, blockID *big.Int) error { return nil } + log.Info("guardian prover signing block", "blockID", blockID.Uint64()) + block, err := p.rpc.L2.BlockByNumber(ctx, blockID) if err != nil { return err @@ -1199,5 +1276,7 @@ func (p *Prover) signBlock(ctx context.Context, blockID *big.Int) error { return err } + log.Info("guardian prover successfully signed block", "blockID", blockID.Uint64()) + return nil } diff --git a/prover/prover_test.go b/prover/prover_test.go index e483ecd25..02af25621 100644 --- a/prover/prover_test.go +++ b/prover/prover_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "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" @@ -18,6 +19,7 @@ import ( "github.com/taikoxyz/taiko-client/bindings/encoding" "github.com/taikoxyz/taiko-client/driver" "github.com/taikoxyz/taiko-client/pkg/jwt" + "github.com/taikoxyz/taiko-client/pkg/rpc" "github.com/taikoxyz/taiko-client/proposer" producer "github.com/taikoxyz/taiko-client/prover/proof_producer" "github.com/taikoxyz/taiko-client/testutils" @@ -42,6 +44,8 @@ func (s *ProverTestSuite) SetupTest() { port, err := strconv.Atoi(proverServerUrl.Port()) s.Nil(err) + allowance := new(big.Int).Exp(big.NewInt(1_000_000_100), big.NewInt(18), nil) + ctx, cancel := context.WithCancel(context.Background()) p := new(Prover) s.Nil(InitFromConfig(ctx, p, (&Config{ @@ -51,6 +55,7 @@ func (s *ProverTestSuite) SetupTest() { L2HttpEndpoint: os.Getenv("L2_EXECUTION_ENGINE_HTTP_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")), GuardianProverAddress: common.HexToAddress(os.Getenv("GUARDIAN_PROVER_CONTRACT_ADDRESS")), L1ProverPrivKey: l1ProverPrivKey, GuardianProverPrivateKey: l1ProverPrivKey, @@ -64,6 +69,7 @@ func (s *ProverTestSuite) SetupTest() { HTTPServerPort: uint64(port), WaitReceiptTimeout: 12 * time.Second, DatabasePath: "", + Allowance: allowance, }))) p.srv = testutils.NewTestProverServer( &s.ClientTestSuite, @@ -141,6 +147,7 @@ func (s *ProverTestSuite) TestInitError() { L2HttpEndpoint: os.Getenv("L2_EXECUTION_ENGINE_HTTP_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")), L1ProverPrivKey: l1ProverPrivKey, GuardianProverPrivateKey: l1ProverPrivKey, Dummy: true, @@ -381,6 +388,50 @@ func (s *ProverTestSuite) TestStartSubscription() { s.NotPanics(s.p.closeSubscription) } +func (s *ProverTestSuite) TestSetApprovalAmount() { + opts, err := bind.NewKeyedTransactorWithChainID(s.p.proverPrivateKey, s.p.rpc.L1ChainID) + s.Nil(err) + + tx, err := s.p.rpc.TaikoToken.Approve(opts, s.p.cfg.TaikoL1Address, common.Big0) + s.Nil(err) + + _, err = rpc.WaitReceipt(context.Background(), s.p.rpc.L1, tx) + s.Nil(err) + + allowance, err := s.p.rpc.TaikoToken.Allowance(&bind.CallOpts{}, s.p.proverAddress, s.p.cfg.TaikoL1Address) + s.Nil(err) + + s.Equal(0, allowance.Cmp(common.Big0)) + + // max that can be approved + amt, ok := new(big.Int).SetString("58764887351446156758749765621197442946723800609510499661540524634076971270144", 10) + s.True(ok) + + s.p.cfg.Allowance = amt + + s.Nil(s.p.setApprovalAmount()) + + allowance, err = s.p.rpc.TaikoToken.Allowance(&bind.CallOpts{}, s.p.proverAddress, s.p.cfg.TaikoL1Address) + s.Nil(err) + + s.Equal(0, amt.Cmp(allowance)) +} + +func (s *ProverTestSuite) TestSetApprovalAlreadySetHigher() { + originalAllowance, err := s.p.rpc.TaikoToken.Allowance(&bind.CallOpts{}, s.p.proverAddress, s.p.cfg.TaikoL1Address) + s.Nil(err) + + amt := big.NewInt(1) + s.p.cfg.Allowance = amt + + s.Nil(s.p.setApprovalAmount()) + + allowance, err := s.p.rpc.TaikoToken.Allowance(&bind.CallOpts{}, s.p.proverAddress, s.p.cfg.TaikoL1Address) + s.Nil(err) + + s.Equal(0, allowance.Cmp(originalAllowance)) +} + func TestProverTestSuite(t *testing.T) { suite.Run(t, new(ProverTestSuite)) }