hermez-node/prover/prover.go
prover/prover.goRaw
1package prover
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "math/big"
8 "net/http"
9 "strings"
10 "time"
11
12 "github.com/dghubble/sling"
13 "github.com/hermeznetwork/hermez-node/common"
14 "github.com/hermeznetwork/tracerr"
15)
16
17// Proof TBD this type will be received from the proof server
18type Proof struct {
19 PiA [2]*big.Int `json:"pi_a"`
20 PiB [3][2]*big.Int `json:"pi_b"`
21 PiC [2]*big.Int `json:"pi_c"`
22 Protocol string `json:"protocol"`
23}
24
25type bigInt big.Int
26
27func (b *bigInt) UnmarshalText(text []byte) error {
28 _, ok := (*big.Int)(b).SetString(string(text), 10)
29 if !ok {
30 return fmt.Errorf("invalid big int: \"%v\"", string(text))
31 }
32 return nil
33}
34
35// UnmarshalJSON unmarshals the proof from a JSON encoded proof with the big
36// ints as strings
37func (p *Proof) UnmarshalJSON(data []byte) error {
38 proof := struct {
39 PiA [2]*bigInt `json:"pi_a"`
40 PiB [3][2]*bigInt `json:"pi_b"`
41 PiC [2]*bigInt `json:"pi_c"`
42 Protocol string `json:"protocol"`
43 }{}
44 if err := json.Unmarshal(data, &proof); err != nil {
45 return err
46 }
47 p.PiA[0] = (*big.Int)(proof.PiA[0])
48 p.PiA[1] = (*big.Int)(proof.PiA[1])
49 p.PiB[0][0] = (*big.Int)(proof.PiB[0][0])
50 p.PiB[0][1] = (*big.Int)(proof.PiB[0][1])
51 p.PiB[1][0] = (*big.Int)(proof.PiB[1][0])
52 p.PiB[1][1] = (*big.Int)(proof.PiB[1][1])
53 p.PiB[2][0] = (*big.Int)(proof.PiB[2][0])
54 p.PiB[2][1] = (*big.Int)(proof.PiB[2][1])
55 p.PiC[0] = (*big.Int)(proof.PiC[0])
56 p.PiC[1] = (*big.Int)(proof.PiC[1])
57 p.Protocol = proof.Protocol
58 return nil
59}
60
61// Client is the interface to a ServerProof that calculates zk proofs
62type Client interface {
63 // Non-blocking
64 CalculateProof(ctx context.Context, zkInputs *common.ZKInputs) error
65 // Blocking
66 GetProof(ctx context.Context) (*Proof, error)
67 // Non-Blocking
68 Cancel(ctx context.Context) error
69 // Blocking
70 WaitReady(ctx context.Context) error
71}
72
73// StatusCode is the status string of the ProofServer
74type StatusCode string
75
76const (
77 // StatusCodeAborted means prover is ready to take new proof. Previous
78 // proof was aborted.
79 StatusCodeAborted StatusCode = "aborted"
80 // StatusCodeBusy means prover is busy computing proof.
81 StatusCodeBusy StatusCode = "busy"
82 // StatusCodeFailed means prover is ready to take new proof. Previous
83 // proof failed
84 StatusCodeFailed StatusCode = "failed"
85 // StatusCodeSuccess means prover is ready to take new proof. Previous
86 // proof succeeded
87 StatusCodeSuccess StatusCode = "success"
88 // StatusCodeUnverified means prover is ready to take new proof.
89 // Previous proof was unverified
90 StatusCodeUnverified StatusCode = "unverified"
91 // StatusCodeUninitialized means prover is not initialized
92 StatusCodeUninitialized StatusCode = "uninitialized"
93 // StatusCodeUndefined means prover is in an undefined state. Most
94 // likely is booting up. Keep trying
95 StatusCodeUndefined StatusCode = "undefined"
96 // StatusCodeInitializing means prover is initializing and not ready yet
97 StatusCodeInitializing StatusCode = "initializing"
98 // StatusCodeReady means prover initialized and ready to do first proof
99 StatusCodeReady StatusCode = "ready"
100)
101
102// IsReady returns true when the prover is ready
103func (status StatusCode) IsReady() bool {
104 if status == StatusCodeAborted || status == StatusCodeFailed || status == StatusCodeSuccess ||
105 status == StatusCodeUnverified || status == StatusCodeReady {
106 return true
107 }
108 return false
109}
110
111// IsInitialized returns true when the prover is initialized
112func (status StatusCode) IsInitialized() bool {
113 if status == StatusCodeUninitialized || status == StatusCodeUndefined ||
114 status == StatusCodeInitializing {
115 return false
116 }
117 return true
118}
119
120// Status is the return struct for the status API endpoint
121type Status struct {
122 Status StatusCode `json:"status"`
123 Proof string `json:"proof"`
124 PubData string `json:"pubData"`
125}
126
127// ErrorServer is the return struct for an API error
128type ErrorServer struct {
129 Status StatusCode `json:"status"`
130 Message string `json:"msg"`
131}
132
133// Error message for ErrorServer
134func (e ErrorServer) Error() string {
135 return fmt.Sprintf("server proof status (%v): %v", e.Status, e.Message)
136}
137
138type apiMethod string
139
140const (
141 // GET is an HTTP GET
142 GET apiMethod = "GET"
143 // POST is an HTTP POST with maybe JSON body
144 POST apiMethod = "POST"
145)
146
147// ProofServerClient contains the data related to a ProofServerClient
148type ProofServerClient struct {
149 URL string
150 client *sling.Sling
151 pollInterval time.Duration
152}
153
154// NewProofServerClient creates a new ServerProof
155func NewProofServerClient(URL string, pollInterval time.Duration) *ProofServerClient {
156 if URL[len(URL)-1] != '/' {
157 URL += "/"
158 }
159 client := sling.New().Base(URL)
160 return &ProofServerClient{URL: URL, client: client, pollInterval: pollInterval}
161}
162
163func (p *ProofServerClient) apiRequest(ctx context.Context, method apiMethod, path string,
164 body interface{}, ret interface{}) error {
165 path = strings.TrimPrefix(path, "/")
166 var errSrv ErrorServer
167 var req *http.Request
168 var err error
169 switch method {
170 case GET:
171 req, err = p.client.New().Get(path).Request()
172 case POST:
173 req, err = p.client.New().Post(path).BodyJSON(body).Request()
174 default:
175 return tracerr.Wrap(fmt.Errorf("invalid http method: %v", method))
176 }
177 if err != nil {
178 return tracerr.Wrap(err)
179 }
180 res, err := p.client.Do(req.WithContext(ctx), ret, &errSrv)
181 if err != nil {
182 return tracerr.Wrap(err)
183 }
184 defer res.Body.Close() //nolint:errcheck
185 if !(200 <= res.StatusCode && res.StatusCode < 300) {
186 return tracerr.Wrap(errSrv)
187 }
188 return nil
189}
190
191func (p *ProofServerClient) apiStatus(ctx context.Context) (*Status, error) {
192 var status Status
193 return &status, tracerr.Wrap(p.apiRequest(ctx, GET, "/status", nil, &status))
194}
195
196func (p *ProofServerClient) apiCancel(ctx context.Context) error {
197 return tracerr.Wrap(p.apiRequest(ctx, POST, "/cancel", nil, nil))
198}
199
200func (p *ProofServerClient) apiInput(ctx context.Context, zkInputs *common.ZKInputs) error {
201 return tracerr.Wrap(p.apiRequest(ctx, POST, "/input", zkInputs, nil))
202}
203
204// CalculateProof sends the *common.ZKInputs to the ServerProof to compute the
205// Proof
206func (p *ProofServerClient) CalculateProof(ctx context.Context, zkInputs *common.ZKInputs) error {
207 return tracerr.Wrap(p.apiInput(ctx, zkInputs))
208}
209
210// GetProof retreives the Proof from the ServerProof, blocking until the proof
211// is ready.
212func (p *ProofServerClient) GetProof(ctx context.Context) (*Proof, error) {
213 if err := p.WaitReady(ctx); err != nil {
214 return nil, err
215 }
216 status, err := p.apiStatus(ctx)
217 if err != nil {
218 return nil, tracerr.Wrap(err)
219 }
220 if status.Status == StatusCodeSuccess {
221 var proof Proof
222 err := json.Unmarshal([]byte(status.Proof), &proof)
223 if err != nil {
224 return nil, tracerr.Wrap(err)
225 }
226 return &proof, nil
227 }
228 return nil, fmt.Errorf("status != StatusCodeSuccess, status = %v", status.Status)
229}
230
231// Cancel cancels any current proof computation
232func (p *ProofServerClient) Cancel(ctx context.Context) error {
233 return tracerr.Wrap(p.apiCancel(ctx))
234}
235
236// WaitReady waits until the serverProof is ready
237func (p *ProofServerClient) WaitReady(ctx context.Context) error {
238 for {
239 status, err := p.apiStatus(ctx)
240 if err != nil {
241 return tracerr.Wrap(err)
242 }
243 if !status.Status.IsInitialized() {
244 return fmt.Errorf("Proof Server is not initialized")
245 }
246 if status.Status.IsReady() {
247 return nil
248 }
249 select {
250 case <-ctx.Done():
251 return tracerr.Wrap(common.ErrDone)
252 case <-time.After(p.pollInterval):
253 }
254 }
255}
256
257// MockClient is a mock ServerProof to be used in tests. It doesn't calculate anything
258type MockClient struct {
259}
260
261// CalculateProof sends the *common.ZKInputs to the ServerProof to compute the
262// Proof
263func (p *MockClient) CalculateProof(ctx context.Context, zkInputs *common.ZKInputs) error {
264 return nil
265}
266
267// GetProof retreives the Proof from the ServerProof
268func (p *MockClient) GetProof(ctx context.Context) (*Proof, error) {
269 // Simulate a delay
270 select {
271 case <-time.After(500 * time.Millisecond): //nolint:gomnd
272 return &Proof{}, nil
273 case <-ctx.Done():
274 return nil, tracerr.Wrap(common.ErrDone)
275 }
276}
277
278// Cancel cancels any current proof computation
279func (p *MockClient) Cancel(ctx context.Context) error {
280 // Simulate a delay
281 select {
282 case <-time.After(80 * time.Millisecond): //nolint:gomnd
283 return nil
284 case <-ctx.Done():
285 return tracerr.Wrap(common.ErrDone)
286 }
287}
288
289// WaitReady waits until the prover is ready
290func (p *MockClient) WaitReady(ctx context.Context) error {
291 // Simulate a delay
292 select {
293 case <-time.After(200 * time.Millisecond): //nolint:gomnd
294 return nil
295 case <-ctx.Done():
296 return tracerr.Wrap(common.ErrDone)
297 }
298}