Skip to content
This repository has been archived by the owner on May 11, 2024. It is now read-only.

fix(prover): Reorder guardian prover sign + add allowance flag/logic #457

Merged
merged 17 commits into from
Dec 1, 2023
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions cmd/flags/prover.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -210,4 +215,5 @@ var ProverFlags = MergeFlags(CommonFlags, []cli.Flag{
DatabasePath,
DatabaseCacheSize,
ProverAssignmentHookAddress,
Allowance,
})
12 changes: 12 additions & 0 deletions prover/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
}
4 changes: 4 additions & 0 deletions prover/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -88,6 +90,7 @@ func (s *ProverTestSuite) TestNewConfigFromCliContextGuardianProver() {
"--" + flags.DatabasePath.Name, "dbPath",
"--" + flags.DatabaseCacheSize.Name, "128",
"--" + flags.MaxProposedIn.Name, "100",
"--" + flags.Allowance.Name, allowance,
}))
}

Expand Down Expand Up @@ -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)
Expand Down
95 changes: 87 additions & 8 deletions prover/prover.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,17 +257,92 @@ 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
}

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)
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 }()

Expand Down Expand Up @@ -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
Expand All @@ -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
}
51 changes: 51 additions & 0 deletions prover/prover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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{
Expand All @@ -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,
Expand All @@ -64,6 +69,7 @@ func (s *ProverTestSuite) SetupTest() {
HTTPServerPort: uint64(port),
WaitReceiptTimeout: 12 * time.Second,
DatabasePath: "",
Allowance: allowance,
})))
p.srv = testutils.NewTestProverServer(
&s.ClientTestSuite,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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))
}