You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

291 lines
7.8 KiB

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)
}
}