diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 61329df4e..4f47fb548 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -48,6 +48,9 @@ var ( ProverSgxProofGeneratedCounter = factory.NewCounter(prometheus.CounterOpts{ Name: "prover_proof_sgx_generated", }) + ProverZkProofGeneratedCounter = factory.NewCounter(prometheus.CounterOpts{ + Name: "prover_proof_zk_generated", + }) ProverSubmissionRevertedCounter = factory.NewCounter(prometheus.CounterOpts{ Name: "prover_proof_submission_reverted", }) diff --git a/prover/init.go b/prover/init.go index 5eb143628..fe603811d 100644 --- a/prover/init.go +++ b/prover/init.go @@ -111,6 +111,21 @@ func (p *Prover) initProofSubmitters( L2Endpoint: p.cfg.RaikoL2Endpoint, Dummy: p.cfg.Dummy, } + case encoding.TierSgxAndZkVMID: + producer = &proofProducer.SGXAndZkVMProofProducer{ + RaikoHostEndpoint: p.cfg.RaikoHostEndpoint, + L1Endpoint: p.cfg.RaikoL1Endpoint, + L1BeaconEndpoint: p.cfg.RaikoL1BeaconEndpoint, + L2Endpoint: p.cfg.RaikoL2Endpoint, + Dummy: p.cfg.Dummy, + SGXProducer: &proofProducer.SGXProofProducer{ + RaikoHostEndpoint: p.cfg.RaikoHostEndpoint, + L1Endpoint: p.cfg.RaikoL1Endpoint, + L1BeaconEndpoint: p.cfg.RaikoL1BeaconEndpoint, + L2Endpoint: p.cfg.RaikoL2Endpoint, + Dummy: p.cfg.Dummy, + }, + } case encoding.TierGuardianID: producer = proofProducer.NewGuardianProofProducer(p.cfg.EnableLivenessBondProof) default: diff --git a/prover/proof_producer/sgx_and_zk_producer.go b/prover/proof_producer/sgx_and_zk_producer.go new file mode 100644 index 000000000..5a2d40142 --- /dev/null +++ b/prover/proof_producer/sgx_and_zk_producer.go @@ -0,0 +1,227 @@ +package producer + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "net/http" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "golang.org/x/sync/errgroup" + + "github.com/taikoxyz/taiko-client/bindings" + "github.com/taikoxyz/taiko-client/bindings/encoding" + "github.com/taikoxyz/taiko-client/internal/metrics" +) + +// R0ProofParam represents the JSON body of SGXRequestProofBodyParam's `risc0` field. +type R0ProofParam struct { + Bonsai bool `json:"bonsai"` + Snark bool `json:"snark"` + Profile bool `json:"profile"` + ExecutionPo2 *big.Int `json:"execution_po2"` +} + +// SGXAndZkVMProofProducer generates an SGX + ZK proof for the given block. +type SGXAndZkVMProofProducer struct { + RaikoHostEndpoint string // a proverd RPC endpoint + L1Endpoint string // a L1 node RPC endpoint + L1BeaconEndpoint string // a L1 beacon node RPC endpoint + L2Endpoint string // a L2 execution engine's RPC endpoint + Dummy bool + SGXProducer *SGXProofProducer + DummyProofProducer +} + +// RequestProof implements the ProofProducer interface. +func (o *SGXAndZkVMProofProducer) RequestProof( + ctx context.Context, + opts *ProofRequestOptions, + blockID *big.Int, + meta *bindings.TaikoDataBlockMetadata, + header *types.Header, +) (*ProofWithHeader, error) { + log.Info( + "Request SGX+ZK proof", + "blockID", blockID, + "coinbase", meta.Coinbase, + "height", header.Number, + "hash", header.Hash(), + ) + + proofs := make([][]byte, 2) + g, ctx := errgroup.WithContext(ctx) + g.Go(func() error { + res, err := o.SGXProducer.RequestProof(ctx, opts, blockID, meta, header) + if err == nil { + proofs[0] = res.Proof + } + return err + }) + g.Go(func() error { + res, err := o.requestZKProof(ctx, opts, blockID, meta, header) + if err == nil { + proofs[1] = res.Proof + } + return err + }) + if err := g.Wait(); err != nil { + return nil, err + } + + return &ProofWithHeader{ + BlockID: blockID, + Meta: meta, + Header: header, + Proof: append(proofs[0], proofs[1]...), + Opts: opts, + Tier: o.Tier(), + }, nil +} + +func (o *SGXAndZkVMProofProducer) requestZKProof( + ctx context.Context, + opts *ProofRequestOptions, + blockID *big.Int, + meta *bindings.TaikoDataBlockMetadata, + header *types.Header, +) (*ProofWithHeader, error) { + log.Info( + "Request ZK proof from raiko-host service", + "blockID", blockID, + "coinbase", meta.Coinbase, + "height", header.Number, + "hash", header.Hash(), + ) + + if o.Dummy { + return o.DummyProofProducer.RequestProof(opts, blockID, meta, header, o.Tier()) + } + + proof, err := o.callProverDaemon(ctx, opts) + if err != nil { + return nil, err + } + + metrics.ProverZkProofGeneratedCounter.Add(1) + + return &ProofWithHeader{ + BlockID: blockID, + Header: header, + Meta: meta, + Proof: proof, + Opts: opts, + Tier: o.Tier(), + }, nil +} + +// callProverDaemon keeps polling the proverd service to get the requested proof. +func (o *SGXAndZkVMProofProducer) callProverDaemon(ctx context.Context, opts *ProofRequestOptions) ([]byte, error) { + var ( + proof []byte + start = time.Now() + ) + if err := backoff.Retry(func() error { + if ctx.Err() != nil { + return nil + } + output, err := o.requestProof(opts) + if err != nil { + log.Error("Failed to request proof", "height", opts.BlockID, "error", err, "endpoint", o.RaikoHostEndpoint) + return err + } + + if output == nil { + log.Info( + "ZK proof generating", + "height", opts.BlockID, + "time", time.Since(start), + "producer", "SGXAndZkVMProofProducer", + ) + return errProofGenerating + } + + log.Debug("ZK proof generation output", "output", output) + + proof = common.Hex2Bytes(output.Proof[2:]) + log.Info( + "ZK proof generated", + "height", opts.BlockID, + "time", time.Since(start), + "producer", "SGXAndZkVMProofProducer", + ) + return nil + }, backoff.WithContext(backoff.NewConstantBackOff(proofPollingInterval), ctx)); err != nil { + return nil, err + } + + return proof, nil +} + +// requestProof sends an RPC request to proverd to try to get the requested proof. +func (o *SGXAndZkVMProofProducer) requestProof(opts *ProofRequestOptions) (*RaikoHostOutput, error) { + reqBody := RaikoRequestProofBody{ + JsonRPC: "2.0", + ID: common.Big1, + Method: "proof", + Params: []*RaikoRequestProofBodyParam{{ + Type: "risc0", + Block: opts.BlockID, + L2RPC: o.L2Endpoint, + L1RPC: o.L1Endpoint, + L1BeaconRPC: o.L1BeaconEndpoint, + Prover: opts.ProverAddress.Hex()[2:], + Graffiti: opts.Graffiti, + R0ProofParam: &R0ProofParam{ + Bonsai: true, + Snark: true, + Profile: false, + ExecutionPo2: new(big.Int).SetUint64(20), + }, + }}, + } + + jsonValue, err := json.Marshal(reqBody) + if err != nil { + return nil, err + } + + res, err := http.Post(o.RaikoHostEndpoint, "application/json", bytes.NewBuffer(jsonValue)) + if err != nil { + return nil, err + } + + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to request proof, id: %d, statusCode: %d", opts.BlockID, res.StatusCode) + } + + resBytes, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + var output RaikoRequestProofBodyResponse + if err := json.Unmarshal(resBytes, &output); err != nil { + return nil, err + } + + if output.Error != nil { + return nil, errors.New(output.Error.Message) + } + + return output.Result, nil +} + +// Tier implements the ProofProducer interface. +func (o *SGXAndZkVMProofProducer) Tier() uint16 { + return encoding.TierSgxAndZkVMID +} diff --git a/prover/proof_producer/sgx_and_zk_producer_test.go b/prover/proof_producer/sgx_and_zk_producer_test.go new file mode 100644 index 000000000..52a2f4a88 --- /dev/null +++ b/prover/proof_producer/sgx_and_zk_producer_test.go @@ -0,0 +1,53 @@ +package producer + +import ( + "context" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + + "github.com/taikoxyz/taiko-client/bindings" + "github.com/taikoxyz/taiko-client/bindings/encoding" +) + +func TestSGXAndZKProducerRequestProof(t *testing.T) { + header := &types.Header{ + ParentHash: randHash(), + UncleHash: randHash(), + Coinbase: common.BytesToAddress(randHash().Bytes()), + Root: randHash(), + TxHash: randHash(), + ReceiptHash: randHash(), + Difficulty: common.Big0, + Number: common.Big256, + GasLimit: 1024, + GasUsed: 1024, + Time: uint64(time.Now().Unix()), + Extra: randHash().Bytes(), + MixDigest: randHash(), + Nonce: types.BlockNonce{}, + } + + var ( + producer = &SGXAndZkVMProofProducer{Dummy: true, SGXProducer: &SGXProofProducer{ + Dummy: true, + }} + blockID = common.Big32 + ) + res, err := producer.RequestProof( + context.Background(), + &ProofRequestOptions{}, + blockID, + &bindings.TaikoDataBlockMetadata{}, + header, + ) + require.Nil(t, err) + + require.Equal(t, res.BlockID, blockID) + require.Equal(t, res.Header, header) + require.Equal(t, res.Tier, encoding.TierSgxAndZkVMID) + require.NotEmpty(t, res.Proof) +} diff --git a/prover/proof_producer/sgx_producer.go b/prover/proof_producer/sgx_producer.go index f306a8dfa..ee83e1f37 100644 --- a/prover/proof_producer/sgx_producer.go +++ b/prover/proof_producer/sgx_producer.go @@ -21,7 +21,7 @@ import ( "github.com/taikoxyz/taiko-client/internal/metrics" ) -// SGXProofProducer generates a SGX proof for the given block. +// SGXProofProducer generates an SGX proof for the given block. type SGXProofProducer struct { RaikoHostEndpoint string // a proverd RPC endpoint L1Endpoint string // a L1 node RPC endpoint @@ -31,35 +31,36 @@ type SGXProofProducer struct { DummyProofProducer } -// SGXRequestProofBody represents the JSON body for requesting the proof. -type SGXRequestProofBody struct { - JsonRPC string `json:"jsonrpc"` //nolint:revive,stylecheck - ID *big.Int `json:"id"` - Method string `json:"method"` - Params []*SGXRequestProofBodyParam `json:"params"` +// RaikoRequestProofBody represents the JSON body for requesting the proof. +type RaikoRequestProofBody struct { + JsonRPC string `json:"jsonrpc"` //nolint:revive,stylecheck + ID *big.Int `json:"id"` + Method string `json:"method"` + Params []*RaikoRequestProofBodyParam `json:"params"` } -// SGXRequestProofBodyParam represents the JSON body of RequestProofBody's `param` field. -type SGXRequestProofBodyParam struct { - Type string `json:"proof_type"` - Block *big.Int `json:"block_number"` - L2RPC string `json:"rpc"` - L1RPC string `json:"l1_rpc"` - L1BeaconRPC string `json:"beacon_rpc"` - Prover string `json:"prover"` - Graffiti string `json:"graffiti"` - ProofParam *ProofParam `json:"sgx"` +// RaikoRequestProofBodyParam represents the JSON body of RequestProofBody's `param` field. +type RaikoRequestProofBodyParam struct { + Type string `json:"proof_type"` + Block *big.Int `json:"block_number"` + L2RPC string `json:"rpc"` + L1RPC string `json:"l1_rpc"` + L1BeaconRPC string `json:"beacon_rpc"` + Prover string `json:"prover"` + Graffiti string `json:"graffiti"` + SgxProofParam *SGXProofParam `json:"sgx"` + R0ProofParam *R0ProofParam `json:"risc0"` } -// ProofParam represents the JSON body of SGXRequestProofBodyParam's `sgx` field. -type ProofParam struct { +// SGXProofParam represents the JSON body of RaikoRequestProofBodyParam's `sgx` field. +type SGXProofParam struct { Setup bool `json:"setup"` Bootstrap bool `json:"bootstrap"` Prove bool `json:"prove"` } -// SGXRequestProofBodyResponse represents the JSON body of the response of the proof requests. -type SGXRequestProofBodyResponse struct { +// RaikoRequestProofBodyResponse represents the JSON body of the response of the proof requests. +type RaikoRequestProofBodyResponse struct { JsonRPC string `json:"jsonrpc"` //nolint:revive,stylecheck ID *big.Int `json:"id"` Result *RaikoHostOutput `json:"result"` @@ -69,7 +70,7 @@ type SGXRequestProofBodyResponse struct { } `json:"error,omitempty"` } -// RaikoHostOutput represents the JSON body of SGXRequestProofBodyResponse's `result` field. +// RaikoHostOutput represents the JSON body of RaikoRequestProofBodyResponse's `result` field. type RaikoHostOutput struct { Proof string `json:"proof"` } @@ -83,7 +84,7 @@ func (s *SGXProofProducer) RequestProof( header *types.Header, ) (*ProofWithHeader, error) { log.Info( - "Request proof from raiko-host service", + "Request SGX proof from raiko-host service", "blockID", blockID, "coinbase", meta.Coinbase, "height", header.Number, @@ -129,7 +130,7 @@ func (s *SGXProofProducer) callProverDaemon(ctx context.Context, opts *ProofRequ if output == nil { log.Info( - "Proof generating", + "SGX proof generating", "height", opts.BlockID, "time", time.Since(start), "producer", "SGXProofProducer", @@ -137,11 +138,11 @@ func (s *SGXProofProducer) callProverDaemon(ctx context.Context, opts *ProofRequ return errProofGenerating } - log.Debug("Proof generation output", "output", output) + log.Debug("SGX proof generation output", "output", output) proof = common.Hex2Bytes(output.Proof[2:]) log.Info( - "Proof generated", + "SGX proof generated", "height", opts.BlockID, "time", time.Since(start), "producer", "SGXProofProducer", @@ -154,13 +155,13 @@ func (s *SGXProofProducer) callProverDaemon(ctx context.Context, opts *ProofRequ return proof, nil } -// requestProof sends a RPC request to proverd to try to get the requested proof. +// requestProof sends an RPC request to proverd to try to get the requested proof. func (s *SGXProofProducer) requestProof(opts *ProofRequestOptions) (*RaikoHostOutput, error) { - reqBody := SGXRequestProofBody{ + reqBody := RaikoRequestProofBody{ JsonRPC: "2.0", ID: common.Big1, Method: "proof", - Params: []*SGXRequestProofBodyParam{{ + Params: []*RaikoRequestProofBodyParam{{ Type: "sgx", Block: opts.BlockID, L2RPC: s.L2Endpoint, @@ -168,7 +169,7 @@ func (s *SGXProofProducer) requestProof(opts *ProofRequestOptions) (*RaikoHostOu L1BeaconRPC: s.L1BeaconEndpoint, Prover: opts.ProverAddress.Hex()[2:], Graffiti: opts.Graffiti, - ProofParam: &ProofParam{ + SgxProofParam: &SGXProofParam{ Setup: false, Bootstrap: false, Prove: true, @@ -196,7 +197,7 @@ func (s *SGXProofProducer) requestProof(opts *ProofRequestOptions) (*RaikoHostOu return nil, err } - var output SGXRequestProofBodyResponse + var output RaikoRequestProofBodyResponse if err := json.Unmarshal(resBytes, &output); err != nil { return nil, err } diff --git a/prover/prover.go b/prover/prover.go index d630a115d..8c06b7398 100644 --- a/prover/prover.go +++ b/prover/prover.go @@ -173,6 +173,7 @@ func InitFromConfig(ctx context.Context, p *Prover, cfg *Config) (err error) { ProverPrivateKey: p.cfg.L1ProverPrivKey, MinOptimisticTierFee: p.cfg.MinOptimisticTierFee, MinSgxTierFee: p.cfg.MinSgxTierFee, + MinSgxAndZkVMTierFee: p.cfg.MinSgxAndZkVMTierFee, MinEthBalance: p.cfg.MinEthBalance, MinTaikoTokenBalance: p.cfg.MinTaikoTokenBalance, MaxExpiry: p.cfg.MaxExpiry,