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