package prover import ( "bytes" "context" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "strings" "time" "github.com/dghubble/sling" "github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/log" "github.com/hermeznetwork/tracerr" ) // Proof TBD this type will be received from the proof server type Proof struct { } // Client is the interface to a ServerProof that calculates zk proofs type Client interface { // Non-blocking CalculateProof(zkInputs *common.ZKInputs) error // Blocking GetProof(ctx context.Context) (*Proof, error) // Non-Blocking Cancel(ctx context.Context) error // Blocking WaitReady(ctx context.Context) error } // StatusCode is the status string of the ProofServer type StatusCode string const ( // StatusCodeAborted means prover is ready to take new proof. Previous // proof was aborted. StatusCodeAborted StatusCode = "aborted" // StatusCodeBusy means prover is busy computing proof. StatusCodeBusy StatusCode = "busy" // StatusCodeFailed means prover is ready to take new proof. Previous // proof failed StatusCodeFailed StatusCode = "failed" // StatusCodeSuccess means prover is ready to take new proof. Previous // proof succeeded StatusCodeSuccess StatusCode = "success" // StatusCodeUnverified means prover is ready to take new proof. // Previous proof was unverified StatusCodeUnverified StatusCode = "unverified" // StatusCodeUninitialized means prover is not initialized StatusCodeUninitialized StatusCode = "uninitialized" // StatusCodeUndefined means prover is in an undefined state. Most // likely is booting up. Keep trying StatusCodeUndefined StatusCode = "undefined" // StatusCodeInitializing means prover is initializing and not ready yet StatusCodeInitializing StatusCode = "initializing" // StatusCodeReady means prover initialized and ready to do first proof StatusCodeReady StatusCode = "ready" ) // IsReady returns true when the prover is ready func (status StatusCode) IsReady() bool { if status == StatusCodeAborted || status == StatusCodeFailed || status == StatusCodeSuccess || status == StatusCodeUnverified || status == StatusCodeReady { return true } return false } // IsInitialized returns true when the prover is initialized func (status StatusCode) IsInitialized() bool { if status == StatusCodeUninitialized || status == StatusCodeUndefined || status == StatusCodeInitializing { return false } return true } // Status is the return struct for the status API endpoint type Status struct { Status StatusCode `json:"status"` Proof string `json:"proof"` PubData string `json:"pubData"` } // ErrorServer is the return struct for an API error type ErrorServer struct { Status StatusCode `json:"status"` Message string `json:"msg"` } // Error message for ErrorServer func (e ErrorServer) Error() string { return fmt.Sprintf("server proof status (%v): %v", e.Status, e.Message) } type apiMethod string const ( // GET is an HTTP GET GET apiMethod = "GET" // POST is an HTTP POST with maybe JSON body POST apiMethod = "POST" // POSTFILE is an HTTP POST with a form file POSTFILE apiMethod = "POSTFILE" ) // ProofServerClient contains the data related to a ProofServerClient type ProofServerClient struct { URL string client *sling.Sling } // NewProofServerClient creates a new ServerProof func NewProofServerClient(URL string) *ProofServerClient { if URL[len(URL)-1] != '/' { URL += "/" } client := sling.New().Base(URL) return &ProofServerClient{URL: URL, client: client} } //nolint:unused type formFileProvider struct { writer *multipart.Writer body []byte } //nolint:unused func newFormFileProvider(payload interface{}) (*formFileProvider, error) { body := new(bytes.Buffer) writer := multipart.NewWriter(body) part, err := writer.CreateFormFile("file", "file.json") if err != nil { return nil, tracerr.Wrap(err) } if err := json.NewEncoder(part).Encode(payload); err != nil { return nil, tracerr.Wrap(err) } if err := writer.Close(); err != nil { return nil, tracerr.Wrap(err) } return &formFileProvider{ writer: writer, body: body.Bytes(), }, nil } func (p formFileProvider) ContentType() string { return p.writer.FormDataContentType() } func (p formFileProvider) Body() (io.Reader, error) { return bytes.NewReader(p.body), nil } //nolint:unused func (p *ProofServerClient) apiRequest(ctx context.Context, method apiMethod, path string, body interface{}, ret interface{}) error { path = strings.TrimPrefix(path, "/") var errSrv ErrorServer var req *http.Request var err error switch method { case GET: req, err = p.client.New().Get(path).Request() case POST: req, err = p.client.New().Post(path).BodyJSON(body).Request() case POSTFILE: provider, err := newFormFileProvider(body) if err != nil { return tracerr.Wrap(err) } req, err = p.client.New().Post(path).BodyProvider(provider).Request() if err != nil { return tracerr.Wrap(err) } default: return tracerr.Wrap(fmt.Errorf("invalid http method: %v", method)) } if err != nil { return tracerr.Wrap(err) } res, err := p.client.Do(req.WithContext(ctx), ret, &errSrv) if err != nil { return tracerr.Wrap(err) } defer res.Body.Close() //nolint:errcheck if !(200 <= res.StatusCode && res.StatusCode < 300) { return tracerr.Wrap(errSrv) } return nil } //nolint:unused func (p *ProofServerClient) apiStatus(ctx context.Context) (*Status, error) { var status Status if err := p.apiRequest(ctx, GET, "/status", nil, &status); err != nil { return nil, tracerr.Wrap(err) } return &status, nil } //nolint:unused func (p *ProofServerClient) apiCancel(ctx context.Context) error { if err := p.apiRequest(ctx, POST, "/cancel", nil, nil); err != nil { return tracerr.Wrap(err) } return nil } //nolint:unused func (p *ProofServerClient) apiInput(ctx context.Context, zkInputs *common.ZKInputs) error { if err := p.apiRequest(ctx, POSTFILE, "/input", zkInputs, nil); err != nil { return tracerr.Wrap(err) } return nil } // CalculateProof sends the *common.ZKInputs to the ServerProof to compute the // Proof func (p *ProofServerClient) CalculateProof(zkInputs *common.ZKInputs) error { log.Error("TODO") return tracerr.Wrap(common.ErrTODO) } // GetProof retreives the Proof from the ServerProof, blocking until the proof // is ready. func (p *ProofServerClient) GetProof(ctx context.Context) (*Proof, error) { log.Error("TODO") return nil, tracerr.Wrap(common.ErrTODO) } // Cancel cancels any current proof computation func (p *ProofServerClient) Cancel(ctx context.Context) error { log.Error("TODO") return tracerr.Wrap(common.ErrTODO) } // WaitReady waits until the serverProof is ready func (p *ProofServerClient) WaitReady(ctx context.Context) error { log.Error("TODO") return tracerr.Wrap(common.ErrTODO) } // MockClient is a mock ServerProof to be used in tests. It doesn't calculate anything type MockClient struct { } // CalculateProof sends the *common.ZKInputs to the ServerProof to compute the // Proof func (p *MockClient) CalculateProof(zkInputs *common.ZKInputs) error { return nil } // GetProof retreives the Proof from the ServerProof func (p *MockClient) GetProof(ctx context.Context) (*Proof, error) { // Simulate a delay select { case <-time.After(500 * time.Millisecond): //nolint:gomnd return &Proof{}, nil case <-ctx.Done(): return nil, tracerr.Wrap(common.ErrDone) } } // Cancel cancels any current proof computation func (p *MockClient) Cancel(ctx context.Context) error { // Simulate a delay select { case <-time.After(80 * time.Millisecond): //nolint:gomnd return nil case <-ctx.Done(): return tracerr.Wrap(common.ErrDone) } } // WaitReady waits until the prover is ready func (p *MockClient) WaitReady(ctx context.Context) error { // Simulate a delay select { case <-time.After(200 * time.Millisecond): //nolint:gomnd return nil case <-ctx.Done(): return tracerr.Wrap(common.ErrDone) } }