Add mock proof serverfeature/sql-semaphore1
@ -0,0 +1,47 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"flag" |
|||
"log" |
|||
"os" |
|||
"os/signal" |
|||
"sync" |
|||
"time" |
|||
|
|||
"github.com/hermeznetwork/hermez-node/test/proofserver" |
|||
) |
|||
|
|||
func main() { |
|||
var addr string |
|||
flag.StringVar(&addr, "a", "localhost:3000", "listen address") |
|||
var provingDuration time.Duration |
|||
flag.DurationVar(&provingDuration, "d", 2*time.Second, "proving time duration") //nolint:gomnd
|
|||
flag.Parse() |
|||
|
|||
mock := proofserver.NewMock(addr, provingDuration) |
|||
ctx, cancel := context.WithCancel(context.Background()) |
|||
var wg sync.WaitGroup |
|||
wg.Add(1) |
|||
go func() { |
|||
if err := mock.Run(ctx); err != nil { |
|||
log.Fatal(err) |
|||
} |
|||
wg.Done() |
|||
}() |
|||
|
|||
stopCh := make(chan interface{}) |
|||
// catch ^C to send the stop signal
|
|||
ossig := make(chan os.Signal, 1) |
|||
signal.Notify(ossig, os.Interrupt) |
|||
go func() { |
|||
for sig := range ossig { |
|||
if sig == os.Interrupt { |
|||
stopCh <- nil |
|||
} |
|||
} |
|||
}() |
|||
<-stopCh |
|||
cancel() |
|||
wg.Wait() |
|||
} |
@ -0,0 +1,191 @@ |
|||
package proofserver |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"sync" |
|||
"time" |
|||
|
|||
"github.com/gin-contrib/cors" |
|||
"github.com/gin-gonic/gin" |
|||
"github.com/hermeznetwork/hermez-node/log" |
|||
"github.com/hermeznetwork/hermez-node/prover" |
|||
"github.com/hermeznetwork/tracerr" |
|||
) |
|||
|
|||
type msg struct { |
|||
value string |
|||
ackCh chan bool |
|||
} |
|||
|
|||
func newMsg(value string) msg { |
|||
return msg{ |
|||
value: value, |
|||
ackCh: make(chan bool), |
|||
} |
|||
} |
|||
|
|||
// Mock proof server
|
|||
type Mock struct { |
|||
addr string |
|||
status prover.StatusCode |
|||
sync.RWMutex |
|||
proof string |
|||
pubData string |
|||
counter int |
|||
msgCh chan msg |
|||
wg sync.WaitGroup |
|||
provingDuration time.Duration |
|||
} |
|||
|
|||
// NewMock creates a new mock server
|
|||
func NewMock(addr string, provingDuration time.Duration) *Mock { |
|||
return &Mock{ |
|||
addr: addr, |
|||
status: prover.StatusCodeReady, |
|||
proof: "", |
|||
pubData: "", |
|||
counter: 0, |
|||
msgCh: make(chan msg), |
|||
provingDuration: provingDuration, |
|||
} |
|||
} |
|||
|
|||
func (s *Mock) err(c *gin.Context, err error) { |
|||
c.JSON(http.StatusInternalServerError, prover.ErrorServer{ |
|||
Status: "error", |
|||
Message: err.Error(), |
|||
}) |
|||
} |
|||
|
|||
func (s *Mock) handleCancel(c *gin.Context) { |
|||
msg := newMsg("cancel") |
|||
s.msgCh <- msg |
|||
<-msg.ackCh |
|||
c.JSON(http.StatusOK, "OK") |
|||
} |
|||
|
|||
func (s *Mock) handleStatus(c *gin.Context) { |
|||
s.RLock() |
|||
c.JSON(http.StatusOK, prover.Status{ |
|||
Status: s.status, |
|||
Proof: s.proof, |
|||
PubData: s.pubData, |
|||
}) |
|||
s.RUnlock() |
|||
} |
|||
|
|||
func (s *Mock) handleInput(c *gin.Context) { |
|||
s.RLock() |
|||
if !s.status.IsReady() { |
|||
s.err(c, fmt.Errorf("not ready")) |
|||
s.RUnlock() |
|||
return |
|||
} |
|||
s.RUnlock() |
|||
_, err := ioutil.ReadAll(c.Request.Body) |
|||
if err != nil { |
|||
s.err(c, err) |
|||
return |
|||
} |
|||
msg := newMsg("prove") |
|||
s.msgCh <- msg |
|||
<-msg.ackCh |
|||
c.JSON(http.StatusOK, "OK") |
|||
} |
|||
|
|||
const longWaitDuration = 999 * time.Hour |
|||
|
|||
// const provingDuration = 2 * time.Second
|
|||
|
|||
func (s *Mock) runProver(ctx context.Context) { |
|||
waitDuration := longWaitDuration |
|||
for { |
|||
select { |
|||
case <-ctx.Done(): |
|||
return |
|||
case msg := <-s.msgCh: |
|||
switch msg.value { |
|||
case "cancel": |
|||
waitDuration = longWaitDuration |
|||
s.Lock() |
|||
if !s.status.IsReady() { |
|||
s.status = prover.StatusCodeAborted |
|||
} |
|||
s.Unlock() |
|||
case "prove": |
|||
waitDuration = s.provingDuration |
|||
s.Lock() |
|||
s.status = prover.StatusCodeBusy |
|||
s.Unlock() |
|||
} |
|||
msg.ackCh <- true |
|||
case <-time.After(waitDuration): |
|||
waitDuration = longWaitDuration |
|||
s.Lock() |
|||
if s.status != prover.StatusCodeBusy { |
|||
s.Unlock() |
|||
continue |
|||
} |
|||
i := s.counter * 100 //nolint:gomnd
|
|||
s.counter++ |
|||
// Mock data
|
|||
s.proof = fmt.Sprintf(`{ |
|||
"pi_a": ["%v", "%v"], |
|||
"pi_b": [["%v", "%v"],["%v", "%v"],["%v", "%v"]], |
|||
"pi_c": ["%v", "%v"], |
|||
"protocol": "groth16" |
|||
}`, i, i+1, i+2, i+3, i+4, i+5, i+6, i+7, i+8, i+9) //nolint:gomnd
|
|||
s.pubData = fmt.Sprintf(`[ |
|||
"%v" |
|||
]`, i+42) //nolint:gomnd
|
|||
s.status = prover.StatusCodeSuccess |
|||
s.Unlock() |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Run the mock server. Use ctx to stop it via cancel
|
|||
func (s *Mock) Run(ctx context.Context) error { |
|||
api := gin.Default() |
|||
api.Use(cors.Default()) |
|||
|
|||
apiGroup := api.Group("/api") |
|||
apiGroup.GET("/status", s.handleStatus) |
|||
apiGroup.POST("/input", s.handleInput) |
|||
apiGroup.POST("/cancel", s.handleCancel) |
|||
|
|||
debugAPIServer := &http.Server{ |
|||
Addr: s.addr, |
|||
Handler: api, |
|||
// Use some hardcoded numberes that are suitable for testing
|
|||
ReadTimeout: 30 * time.Second, //nolint:gomnd
|
|||
WriteTimeout: 30 * time.Second, //nolint:gomnd
|
|||
MaxHeaderBytes: 1 << 20, //nolint:gomnd
|
|||
} |
|||
go func() { |
|||
log.Infof("prover.MockServer is ready at %v", s.addr) |
|||
if err := debugAPIServer.ListenAndServe(); err != nil && tracerr.Unwrap(err) != http.ErrServerClosed { |
|||
log.Fatalf("Listen: %s\n", err) |
|||
} |
|||
}() |
|||
s.wg.Add(1) |
|||
go func() { |
|||
s.runProver(ctx) |
|||
s.wg.Done() |
|||
}() |
|||
|
|||
<-ctx.Done() |
|||
log.Info("Stopping prover.MockServer...") |
|||
|
|||
s.wg.Wait() |
|||
ctxTimeout, cancel := context.WithTimeout(context.Background(), 10*time.Second) //nolint:gomnd
|
|||
defer cancel() |
|||
if err := debugAPIServer.Shutdown(ctxTimeout); err != nil { |
|||
return tracerr.Wrap(err) |
|||
} |
|||
log.Info("prover.MockServer done") |
|||
return nil |
|||
} |