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}