diff --git a/prover/README.md b/prover/README.md new file mode 100644 index 0000000..9cd630d --- /dev/null +++ b/prover/README.md @@ -0,0 +1,28 @@ +## Test Prover + +### Server Proof API +It is necessary to have a docker with server locally. + +The instructions in the following link can be followed: +https://github.com/hermeznetwork/test-info/tree/main/cli-prover + +> It is necessary to consult the pre-requirements to follow the steps of the next summary + +A summary of the steps to follow to run docker would be: + +- Clone the repository: https://github.com/hermeznetwork/test-info +- `cd cli-prover` +- `./cli-prover.sh -s localhost -v ~/prover_data -r 22` +- To enter docker: `docker exec -ti docker_cusnarks bash` +- Inside the docker: `cd cusnarks; make docker_all FORCE_CPU=1` +- Inside the docker: `cd config; python3 cusnarks_config.py 22 BN256` +- To exit docker: `exit` +- Now, the server API can be used. Helper can be consulted with: `./cli-prover.sh -h` +- Is necessary to initialize the server with: `./cli-prover.sh --post-start ` +- When `./cli-prover.sh --get-status ` is `ready` can be run the test. + +> The session can be consulted with `tmux ls`. The session will be the number of the last session on the list. + +### Test + +`INTEGRATION=1 go test` \ No newline at end of file diff --git a/prover/prover.go b/prover/prover.go index acec14f..124cb4b 100644 --- a/prover/prover.go +++ b/prover/prover.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "mime/multipart" @@ -13,18 +14,21 @@ import ( "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 { + PiA []string `json:"pi_a"` + PiB [][]string `json:"pi_b"` + PiC []string `json:"pi_c"` + Protocol string `json:"protocol"` } // Client is the interface to a ServerProof that calculates zk proofs type Client interface { // Non-blocking - CalculateProof(zkInputs *common.ZKInputs) error + CalculateProof(ctx context.Context, zkInputs *common.ZKInputs) error // Blocking GetProof(ctx context.Context) (*Proof, error) // Non-Blocking @@ -105,23 +109,22 @@ const ( 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 + URL string + client *sling.Sling + timeCons time.Duration } // NewProofServerClient creates a new ServerProof -func NewProofServerClient(URL string) *ProofServerClient { +func NewProofServerClient(URL string, timeCons time.Duration) *ProofServerClient { if URL[len(URL)-1] != '/' { URL += "/" } client := sling.New().Base(URL) - return &ProofServerClient{URL: URL, client: client} + return &ProofServerClient{URL: URL, client: client, timeCons: timeCons} } //nolint:unused @@ -170,15 +173,6 @@ func (p *ProofServerClient) apiRequest(ctx context.Context, method apiMethod, pa 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)) } @@ -215,7 +209,7 @@ func (p *ProofServerClient) apiCancel(ctx context.Context) error { //nolint:unused func (p *ProofServerClient) apiInput(ctx context.Context, zkInputs *common.ZKInputs) error { - if err := p.apiRequest(ctx, POSTFILE, "/input", zkInputs, nil); err != nil { + if err := p.apiRequest(ctx, POST, "/input", zkInputs, nil); err != nil { return tracerr.Wrap(err) } return nil @@ -223,28 +217,70 @@ func (p *ProofServerClient) apiInput(ctx context.Context, zkInputs *common.ZKInp // 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) +func (p *ProofServerClient) CalculateProof(ctx context.Context, zkInputs *common.ZKInputs) error { + err := p.apiInput(ctx, zkInputs) + if err != nil { + return tracerr.Wrap(err) + } + return nil } // 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) + status, err := p.apiStatus(ctx) + if err != nil { + return nil, tracerr.Wrap(err) + } + if status.Status == StatusCodeSuccess { + var proof Proof + err := json.Unmarshal([]byte(status.Proof), &proof) + if err != nil { + return nil, tracerr.Wrap(err) + } + return &proof, nil + } else { + return nil, errors.New("State is not Success") + } } // Cancel cancels any current proof computation func (p *ProofServerClient) Cancel(ctx context.Context) error { - log.Error("TODO") - return tracerr.Wrap(common.ErrTODO) + err := p.apiCancel(ctx) + if err != nil { + return tracerr.Wrap(err) + } + return nil } // WaitReady waits until the serverProof is ready func (p *ProofServerClient) WaitReady(ctx context.Context) error { - log.Error("TODO") - return tracerr.Wrap(common.ErrTODO) + status, err := p.apiStatus(ctx) + if err != nil { + return tracerr.Wrap(err) + } + if !status.Status.IsInitialized() { + err := errors.New("Proof Server is not initialized") + return err + } else { + if status.Status.IsReady() { + return nil + } + for { + select { + case <-ctx.Done(): + return tracerr.Wrap(common.ErrDone) + case <-time.After(p.timeCons): + status, err := p.apiStatus(ctx) + if err != nil { + return tracerr.Wrap(err) + } + if status.Status.IsReady() { + return nil + } + } + } + } } // MockClient is a mock ServerProof to be used in tests. It doesn't calculate anything diff --git a/prover/prover_test.go b/prover/prover_test.go new file mode 100644 index 0000000..6bfedc3 --- /dev/null +++ b/prover/prover_test.go @@ -0,0 +1,83 @@ +package prover + +import ( + "context" + "math/big" + "os" + "testing" + "time" + + "github.com/hermeznetwork/hermez-node/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const apiURL = "http://localhost:3000/api" +const timeCons = 1 * time.Second + +var proofServerClient *ProofServerClient + +func TestMain(m *testing.M) { + exitVal := 0 + if os.Getenv("INTEGRATION") != "" { + proofServerClient = NewProofServerClient(apiURL, timeCons) + err := proofServerClient.WaitReady(context.Background()) + if err != nil { + panic(err) + } + exitVal = m.Run() + } + os.Exit(exitVal) +} + +func TestApiServer(t *testing.T) { + t.Run("testAPIStatus", testAPIStatus) + t.Run("testCalculateProof", testCalculateProof) + time.Sleep(time.Second / 4) + err := proofServerClient.WaitReady(context.Background()) + require.NoError(t, err) + t.Run("testGetProof", testGetProof) + t.Run("testCancel", testCancel) +} + +func testAPIStatus(t *testing.T) { + status, err := proofServerClient.apiStatus(context.Background()) + require.NoError(t, err) + assert.Equal(t, true, status.Status.IsReady()) +} + +func testCalculateProof(t *testing.T) { + var zkInputs *common.ZKInputs + zkInputs = common.NewZKInputs(100, 16, 512, 24, 32, big.NewInt(1)) + err := proofServerClient.CalculateProof(context.Background(), zkInputs) + require.NoError(t, err) +} + +func testGetProof(t *testing.T) { + proof, err := proofServerClient.GetProof(context.Background()) + require.NoError(t, err) + require.NotNil(t, proof) + require.NotNil(t, proof.PiA) + require.NotNil(t, proof.PiB) + require.NotNil(t, proof.PiC) + require.NotNil(t, proof.Protocol) +} + +func testCancel(t *testing.T) { + var zkInputs *common.ZKInputs + zkInputs = common.NewZKInputs(100, 16, 512, 24, 32, big.NewInt(1)) + err := proofServerClient.CalculateProof(context.Background(), zkInputs) + require.NoError(t, err) + // TODO: remove sleep when the server has been reviewed + time.Sleep(time.Second / 4) + err = proofServerClient.Cancel(context.Background()) + require.NoError(t, err) + status, err := proofServerClient.apiStatus(context.Background()) + require.NoError(t, err) + for status.Status == StatusCodeBusy { + time.Sleep(proofServerClient.timeCons) + status, err = proofServerClient.apiStatus(context.Background()) + require.NoError(t, err) + } + assert.Equal(t, StatusCodeAborted, status.Status) +}