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,deadcode
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	}
243	return nil, errors.New("State is not Success")
244}
245
246// Cancel cancels any current proof computation
247func (p *ProofServerClient) Cancel(ctx context.Context) error {
248	err := p.apiCancel(ctx)
249	if err != nil {
250		return tracerr.Wrap(err)
251	}
252	return nil
253}
254
255// WaitReady waits until the serverProof is ready
256func (p *ProofServerClient) WaitReady(ctx context.Context) error {
257	status, err := p.apiStatus(ctx)
258	if err != nil {
259		return tracerr.Wrap(err)
260	}
261	if !status.Status.IsInitialized() {
262		err := errors.New("Proof Server is not initialized")
263		return err
264	}
265	if status.Status.IsReady() {
266		return nil
267	}
268	for {
269		select {
270		case <-ctx.Done():
271			return tracerr.Wrap(common.ErrDone)
272		case <-time.After(p.timeCons):
273			status, err := p.apiStatus(ctx)
274			if err != nil {
275				return tracerr.Wrap(err)
276			}
277			if status.Status.IsReady() {
278				return nil
279			}
280		}
281	}
282}
283
284// MockClient is a mock ServerProof to be used in tests.  It doesn't calculate anything
285type MockClient struct {
286}
287
288// CalculateProof sends the *common.ZKInputs to the ServerProof to compute the
289// Proof
290func (p *MockClient) CalculateProof(ctx context.Context, zkInputs *common.ZKInputs) error {
291	return nil
292}
293
294// GetProof retreives the Proof from the ServerProof
295func (p *MockClient) GetProof(ctx context.Context) (*Proof, error) {
296	// Simulate a delay
297	select {
298	case <-time.After(500 * time.Millisecond): //nolint:gomnd
299		return &Proof{}, nil
300	case <-ctx.Done():
301		return nil, tracerr.Wrap(common.ErrDone)
302	}
303}
304
305// Cancel cancels any current proof computation
306func (p *MockClient) Cancel(ctx context.Context) error {
307	// Simulate a delay
308	select {
309	case <-time.After(80 * time.Millisecond): //nolint:gomnd
310		return nil
311	case <-ctx.Done():
312		return tracerr.Wrap(common.ErrDone)
313	}
314}
315
316// WaitReady waits until the prover is ready
317func (p *MockClient) WaitReady(ctx context.Context) error {
318	// Simulate a delay
319	select {
320	case <-time.After(200 * time.Millisecond): //nolint:gomnd
321		return nil
322	case <-ctx.Done():
323		return tracerr.Wrap(common.ErrDone)
324	}
325}