arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/prover/prover.go
prover/prover.goRaw
  1package prover
  2
  3import (
  4	"context"
  5	"encoding/json"
  6	"fmt"
  7	"math/big"
  8	"net/http"
  9	"strings"
 10	"time"
 11
 12	"github.com/dghubble/sling"
 13	"github.com/hermeznetwork/hermez-node/common"
 14	"github.com/hermeznetwork/tracerr"
 15)
 16
 17// Proof TBD this type will be received from the proof server
 18type Proof struct {
 19	PiA      [2]*big.Int    `json:"pi_a"`
 20	PiB      [3][2]*big.Int `json:"pi_b"`
 21	PiC      [2]*big.Int    `json:"pi_c"`
 22	Protocol string         `json:"protocol"`
 23}
 24
 25type bigInt big.Int
 26
 27func (b *bigInt) UnmarshalText(text []byte) error {
 28	_, ok := (*big.Int)(b).SetString(string(text), 10)
 29	if !ok {
 30		return fmt.Errorf("invalid big int: \"%v\"", string(text))
 31	}
 32	return nil
 33}
 34
 35// UnmarshalJSON unmarshals the proof from a JSON encoded proof with the big
 36// ints as strings
 37func (p *Proof) UnmarshalJSON(data []byte) error {
 38	proof := struct {
 39		PiA      [2]*bigInt    `json:"pi_a"`
 40		PiB      [3][2]*bigInt `json:"pi_b"`
 41		PiC      [2]*bigInt    `json:"pi_c"`
 42		Protocol string        `json:"protocol"`
 43	}{}
 44	if err := json.Unmarshal(data, &proof); err != nil {
 45		return err
 46	}
 47	p.PiA[0] = (*big.Int)(proof.PiA[0])
 48	p.PiA[1] = (*big.Int)(proof.PiA[1])
 49	p.PiB[0][0] = (*big.Int)(proof.PiB[0][0])
 50	p.PiB[0][1] = (*big.Int)(proof.PiB[0][1])
 51	p.PiB[1][0] = (*big.Int)(proof.PiB[1][0])
 52	p.PiB[1][1] = (*big.Int)(proof.PiB[1][1])
 53	p.PiB[2][0] = (*big.Int)(proof.PiB[2][0])
 54	p.PiB[2][1] = (*big.Int)(proof.PiB[2][1])
 55	p.PiC[0] = (*big.Int)(proof.PiC[0])
 56	p.PiC[1] = (*big.Int)(proof.PiC[1])
 57	p.Protocol = proof.Protocol
 58	return nil
 59}
 60
 61// Client is the interface to a ServerProof that calculates zk proofs
 62type Client interface {
 63	// Non-blocking
 64	CalculateProof(ctx context.Context, zkInputs *common.ZKInputs) error
 65	// Blocking
 66	GetProof(ctx context.Context) (*Proof, error)
 67	// Non-Blocking
 68	Cancel(ctx context.Context) error
 69	// Blocking
 70	WaitReady(ctx context.Context) error
 71}
 72
 73// StatusCode is the status string of the ProofServer
 74type StatusCode string
 75
 76const (
 77	// StatusCodeAborted means prover is ready to take new proof. Previous
 78	// proof was aborted.
 79	StatusCodeAborted StatusCode = "aborted"
 80	// StatusCodeBusy means prover is busy computing proof.
 81	StatusCodeBusy StatusCode = "busy"
 82	// StatusCodeFailed means prover is ready to take new proof. Previous
 83	// proof failed
 84	StatusCodeFailed StatusCode = "failed"
 85	// StatusCodeSuccess means prover is ready to take new proof. Previous
 86	// proof succeeded
 87	StatusCodeSuccess StatusCode = "success"
 88	// StatusCodeUnverified means prover is ready to take new proof.
 89	// Previous proof was unverified
 90	StatusCodeUnverified StatusCode = "unverified"
 91	// StatusCodeUninitialized means prover is not initialized
 92	StatusCodeUninitialized StatusCode = "uninitialized"
 93	// StatusCodeUndefined means prover is in an undefined state. Most
 94	// likely is booting up. Keep trying
 95	StatusCodeUndefined StatusCode = "undefined"
 96	// StatusCodeInitializing means prover is initializing and not ready yet
 97	StatusCodeInitializing StatusCode = "initializing"
 98	// StatusCodeReady means prover initialized and ready to do first proof
 99	StatusCodeReady StatusCode = "ready"
100)
101
102// IsReady returns true when the prover is ready
103func (status StatusCode) IsReady() bool {
104	if status == StatusCodeAborted || status == StatusCodeFailed || status == StatusCodeSuccess ||
105		status == StatusCodeUnverified || status == StatusCodeReady {
106		return true
107	}
108	return false
109}
110
111// IsInitialized returns true when the prover is initialized
112func (status StatusCode) IsInitialized() bool {
113	if status == StatusCodeUninitialized || status == StatusCodeUndefined ||
114		status == StatusCodeInitializing {
115		return false
116	}
117	return true
118}
119
120// Status is the return struct for the status API endpoint
121type Status struct {
122	Status  StatusCode `json:"status"`
123	Proof   string     `json:"proof"`
124	PubData string     `json:"pubData"`
125}
126
127// ErrorServer is the return struct for an API error
128type ErrorServer struct {
129	Status  StatusCode `json:"status"`
130	Message string     `json:"msg"`
131}
132
133// Error message for ErrorServer
134func (e ErrorServer) Error() string {
135	return fmt.Sprintf("server proof status (%v): %v", e.Status, e.Message)
136}
137
138type apiMethod string
139
140const (
141	// GET is an HTTP GET
142	GET apiMethod = "GET"
143	// POST is an HTTP POST with maybe JSON body
144	POST apiMethod = "POST"
145)
146
147// ProofServerClient contains the data related to a ProofServerClient
148type ProofServerClient struct {
149	URL          string
150	client       *sling.Sling
151	pollInterval time.Duration
152}
153
154// NewProofServerClient creates a new ServerProof
155func NewProofServerClient(URL string, pollInterval time.Duration) *ProofServerClient {
156	if URL[len(URL)-1] != '/' {
157		URL += "/"
158	}
159	client := sling.New().Base(URL)
160	return &ProofServerClient{URL: URL, client: client, pollInterval: pollInterval}
161}
162
163func (p *ProofServerClient) apiRequest(ctx context.Context, method apiMethod, path string,
164	body interface{}, ret interface{}) error {
165	path = strings.TrimPrefix(path, "/")
166	var errSrv ErrorServer
167	var req *http.Request
168	var err error
169	switch method {
170	case GET:
171		req, err = p.client.New().Get(path).Request()
172	case POST:
173		req, err = p.client.New().Post(path).BodyJSON(body).Request()
174	default:
175		return tracerr.Wrap(fmt.Errorf("invalid http method: %v", method))
176	}
177	if err != nil {
178		return tracerr.Wrap(err)
179	}
180	res, err := p.client.Do(req.WithContext(ctx), ret, &errSrv)
181	if err != nil {
182		return tracerr.Wrap(err)
183	}
184	defer res.Body.Close() //nolint:errcheck
185	if !(200 <= res.StatusCode && res.StatusCode < 300) {
186		return tracerr.Wrap(errSrv)
187	}
188	return nil
189}
190
191func (p *ProofServerClient) apiStatus(ctx context.Context) (*Status, error) {
192	var status Status
193	return &status, tracerr.Wrap(p.apiRequest(ctx, GET, "/status", nil, &status))
194}
195
196func (p *ProofServerClient) apiCancel(ctx context.Context) error {
197	return tracerr.Wrap(p.apiRequest(ctx, POST, "/cancel", nil, nil))
198}
199
200func (p *ProofServerClient) apiInput(ctx context.Context, zkInputs *common.ZKInputs) error {
201	return tracerr.Wrap(p.apiRequest(ctx, POST, "/input", zkInputs, nil))
202}
203
204// CalculateProof sends the *common.ZKInputs to the ServerProof to compute the
205// Proof
206func (p *ProofServerClient) CalculateProof(ctx context.Context, zkInputs *common.ZKInputs) error {
207	return tracerr.Wrap(p.apiInput(ctx, zkInputs))
208}
209
210// GetProof retreives the Proof from the ServerProof, blocking until the proof
211// is ready.
212func (p *ProofServerClient) GetProof(ctx context.Context) (*Proof, error) {
213	if err := p.WaitReady(ctx); err != nil {
214		return nil, err
215	}
216	status, err := p.apiStatus(ctx)
217	if err != nil {
218		return nil, tracerr.Wrap(err)
219	}
220	if status.Status == StatusCodeSuccess {
221		var proof Proof
222		err := json.Unmarshal([]byte(status.Proof), &proof)
223		if err != nil {
224			return nil, tracerr.Wrap(err)
225		}
226		return &proof, nil
227	}
228	return nil, fmt.Errorf("status != StatusCodeSuccess, status = %v", status.Status)
229}
230
231// Cancel cancels any current proof computation
232func (p *ProofServerClient) Cancel(ctx context.Context) error {
233	return tracerr.Wrap(p.apiCancel(ctx))
234}
235
236// WaitReady waits until the serverProof is ready
237func (p *ProofServerClient) WaitReady(ctx context.Context) error {
238	for {
239		status, err := p.apiStatus(ctx)
240		if err != nil {
241			return tracerr.Wrap(err)
242		}
243		if !status.Status.IsInitialized() {
244			return fmt.Errorf("Proof Server is not initialized")
245		}
246		if status.Status.IsReady() {
247			return nil
248		}
249		select {
250		case <-ctx.Done():
251			return tracerr.Wrap(common.ErrDone)
252		case <-time.After(p.pollInterval):
253		}
254	}
255}
256
257// MockClient is a mock ServerProof to be used in tests.  It doesn't calculate anything
258type MockClient struct {
259}
260
261// CalculateProof sends the *common.ZKInputs to the ServerProof to compute the
262// Proof
263func (p *MockClient) CalculateProof(ctx context.Context, zkInputs *common.ZKInputs) error {
264	return nil
265}
266
267// GetProof retreives the Proof from the ServerProof
268func (p *MockClient) GetProof(ctx context.Context) (*Proof, error) {
269	// Simulate a delay
270	select {
271	case <-time.After(500 * time.Millisecond): //nolint:gomnd
272		return &Proof{}, nil
273	case <-ctx.Done():
274		return nil, tracerr.Wrap(common.ErrDone)
275	}
276}
277
278// Cancel cancels any current proof computation
279func (p *MockClient) Cancel(ctx context.Context) error {
280	// Simulate a delay
281	select {
282	case <-time.After(80 * time.Millisecond): //nolint:gomnd
283		return nil
284	case <-ctx.Done():
285		return tracerr.Wrap(common.ErrDone)
286	}
287}
288
289// WaitReady waits until the prover is ready
290func (p *MockClient) WaitReady(ctx context.Context) error {
291	// Simulate a delay
292	select {
293	case <-time.After(200 * time.Millisecond): //nolint:gomnd
294		return nil
295	case <-ctx.Done():
296		return tracerr.Wrap(common.ErrDone)
297	}
298}