Skip to content

Commit 2375164

Browse files
authored
support prover task query api (#704)
* support prover task query api * fix unit test
1 parent aa1ae1f commit 2375164

File tree

9 files changed

+165
-11
lines changed

9 files changed

+165
-11
lines changed

cmd/apinode/api/http.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"bytes"
55
"crypto/ecdsa"
66
"encoding/json"
7+
"fmt"
8+
"io"
79
"log/slog"
810
"net/http"
911
"strings"
@@ -16,6 +18,7 @@ import (
1618
"github.com/pkg/errors"
1719

1820
"github.com/iotexproject/w3bstream/cmd/apinode/persistence"
21+
proverapi "github.com/iotexproject/w3bstream/cmd/prover/api"
1922
"github.com/iotexproject/w3bstream/p2p"
2023
)
2124

@@ -47,6 +50,7 @@ type StateLog struct {
4750
State string `json:"state"`
4851
Time time.Time `json:"time"`
4952
Comment string `json:"comment,omitempty"`
53+
Error string `json:"error,omitempty"`
5054
Tx string `json:"transaction_hash,omitempty"`
5155
ProverID string `json:"prover_id,omitempty"`
5256
}
@@ -63,6 +67,7 @@ type httpServer struct {
6367
aggregationAmount int
6468
prv *ecdsa.PrivateKey
6569
pubSub *p2p.PubSub
70+
proverAddr string
6671
}
6772

6873
func (s *httpServer) handleMessage(c *gin.Context) {
@@ -176,6 +181,50 @@ func (s *httpServer) queryTask(c *gin.Context) {
176181
return
177182
}
178183
if ts == nil {
184+
reqJ, err := json.Marshal(proverapi.QueryTaskReq{
185+
ProjectID: req.ProjectID,
186+
TaskID: req.TaskID,
187+
})
188+
if err != nil {
189+
slog.Error("failed to marshal prover request", "error", err)
190+
c.JSON(http.StatusInternalServerError, NewErrResp(errors.Wrap(err, "failed to marshal prover request")))
191+
return
192+
}
193+
req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/task", s.proverAddr), bytes.NewBuffer(reqJ))
194+
if err != nil {
195+
slog.Error("failed to build http request", "error", err)
196+
c.JSON(http.StatusInternalServerError, NewErrResp(errors.Wrap(err, "failed to build http request")))
197+
return
198+
}
199+
req.Header.Set("Content-Type", "application/json")
200+
201+
proverResp, err := http.DefaultClient.Do(req)
202+
if err != nil {
203+
slog.Error("failed to call prover http server", "error", err)
204+
c.JSON(http.StatusInternalServerError, NewErrResp(errors.Wrap(err, "failed to call prover http server")))
205+
return
206+
}
207+
defer proverResp.Body.Close()
208+
209+
body, err := io.ReadAll(proverResp.Body)
210+
if err != nil {
211+
slog.Error("failed to read prover http server response", "error", err)
212+
c.JSON(http.StatusInternalServerError, NewErrResp(errors.Wrap(err, "failed to read prover http server response")))
213+
return
214+
}
215+
taskResp := &proverapi.QueryTaskResp{}
216+
if err := json.Unmarshal(body, &taskResp); err != nil {
217+
slog.Error("failed to unmarshal prover http server response", "error", err)
218+
c.JSON(http.StatusInternalServerError, NewErrResp(errors.Wrap(err, "failed to unmarshal prover http server response")))
219+
return
220+
}
221+
if taskResp.Processed && taskResp.Error != "" {
222+
resp.States = append(resp.States, &StateLog{
223+
State: "failed",
224+
Error: taskResp.Error,
225+
Time: taskResp.Time,
226+
})
227+
}
179228
c.JSON(http.StatusOK, resp)
180229
return
181230
}
@@ -189,20 +238,21 @@ func (s *httpServer) queryTask(c *gin.Context) {
189238
}
190239

191240
// this func will block caller
192-
func Run(p *persistence.Persistence, prv *ecdsa.PrivateKey, pubSub *p2p.PubSub, aggregationAmount int, address string) error {
241+
func Run(p *persistence.Persistence, prv *ecdsa.PrivateKey, pubSub *p2p.PubSub, aggregationAmount int, addr, proverAddr string) error {
193242
s := &httpServer{
194243
engine: gin.Default(),
195244
p: p,
196245
aggregationAmount: aggregationAmount,
197246
prv: prv,
198247
pubSub: pubSub,
248+
proverAddr: proverAddr,
199249
}
200250

201251
s.engine.POST("/message", s.handleMessage)
202252
s.engine.GET("/task", s.queryTask)
203253

204-
if err := s.engine.Run(address); err != nil {
205-
slog.Error("failed to start http server", "address", address, "error", err)
254+
if err := s.engine.Run(addr); err != nil {
255+
slog.Error("failed to start http server", "address", addr, "error", err)
206256
return errors.Wrap(err, "could not start http server; check if the address is in use or network is accessible")
207257
}
208258
return nil

cmd/apinode/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
type Config struct {
1111
LogLevel slog.Level `env:"LOG_LEVEL,optional"`
1212
ServiceEndpoint string `env:"HTTP_SERVICE_ENDPOINT"`
13+
ProverServiceEndpoint string `env:"PROVER_SERVICE_ENDPOINT"`
1314
AggregationAmount int `env:"AGGREGATION_AMOUNT,optional"`
1415
DatabaseDSN string `env:"DATABASE_DSN"`
1516
PrvKey string `env:"PRIVATE_KEY,optional"`
@@ -24,6 +25,7 @@ type Config struct {
2425
var defaultTestnetConfig = &Config{
2526
LogLevel: slog.LevelInfo,
2627
ServiceEndpoint: ":9000",
28+
ProverServiceEndpoint: "localhost:9002",
2729
AggregationAmount: 1,
2830
DatabaseDSN: "postgres://postgres:mysecretpassword@postgres:5432/w3bstream?sslmode=disable",
2931
PrvKey: "dbfe03b0406549232b8dccc04be8224fcc0afa300a33d4f335dcfdfead861c85",

cmd/apinode/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func main() {
6161
}
6262

6363
go func() {
64-
if err := api.Run(p, prv, pubSub, cfg.AggregationAmount, cfg.ServiceEndpoint); err != nil {
64+
if err := api.Run(p, prv, pubSub, cfg.AggregationAmount, cfg.ServiceEndpoint, cfg.ProverServiceEndpoint); err != nil {
6565
log.Fatal(err)
6666
}
6767
}()

cmd/prover/api/http.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package api
2+
3+
import (
4+
"log/slog"
5+
"net/http"
6+
"time"
7+
8+
"github.com/ethereum/go-ethereum/common"
9+
"github.com/gin-gonic/gin"
10+
"github.com/pkg/errors"
11+
12+
"github.com/iotexproject/w3bstream/cmd/prover/db"
13+
)
14+
15+
type ErrResp struct {
16+
Error string `json:"error,omitempty"`
17+
}
18+
19+
func NewErrResp(err error) *ErrResp {
20+
return &ErrResp{Error: err.Error()}
21+
}
22+
23+
type QueryTaskReq struct {
24+
ProjectID uint64 `json:"projectID" binding:"required"`
25+
TaskID string `json:"taskID" binding:"required"`
26+
}
27+
28+
type QueryTaskResp struct {
29+
Time time.Time `json:"time"`
30+
Processed bool `json:"processed"`
31+
Error string `json:"error,omitempty"`
32+
}
33+
34+
type httpServer struct {
35+
engine *gin.Engine
36+
db *db.DB
37+
}
38+
39+
func (s *httpServer) queryTask(c *gin.Context) {
40+
req := &QueryTaskReq{}
41+
if err := c.ShouldBindJSON(req); err != nil {
42+
slog.Error("failed to bind request", "error", err)
43+
c.JSON(http.StatusBadRequest, NewErrResp(errors.Wrap(err, "invalid request payload")))
44+
return
45+
}
46+
taskID := common.HexToHash(req.TaskID)
47+
48+
processed, errMsg, createdAt, err := s.db.ProcessedTask(req.ProjectID, taskID)
49+
if err != nil {
50+
slog.Error("failed to query processed task", "error", err)
51+
c.JSON(http.StatusInternalServerError, NewErrResp(errors.Wrap(err, "failed to query processed task")))
52+
return
53+
}
54+
55+
c.JSON(http.StatusOK, &QueryTaskResp{
56+
Time: createdAt,
57+
Processed: processed,
58+
Error: errMsg,
59+
})
60+
}
61+
62+
// this func will block caller
63+
func Run(db *db.DB, address string) error {
64+
s := &httpServer{
65+
engine: gin.Default(),
66+
db: db,
67+
}
68+
69+
s.engine.GET("/task", s.queryTask)
70+
71+
if err := s.engine.Run(address); err != nil {
72+
slog.Error("failed to start http server", "address", address, "error", err)
73+
return errors.Wrap(err, "could not start http server; check if the address is in use or network is accessible")
74+
}
75+
return nil
76+
}

cmd/prover/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
type Config struct {
1111
LogLevel slog.Level `env:"LOG_LEVEL,optional"`
12+
ServiceEndpoint string `env:"HTTP_SERVICE_ENDPOINT"`
1213
VMEndpoints string `env:"VM_ENDPOINTS"`
1314
DatasourceDSN string `env:"DATASOURCE_DSN"`
1415
ChainEndpoint string `env:"CHAIN_ENDPOINT,optional"`
@@ -24,6 +25,7 @@ type Config struct {
2425
var (
2526
defaultTestnetConfig = &Config{
2627
LogLevel: slog.LevelInfo,
28+
ServiceEndpoint: ":9002",
2729
VMEndpoints: `{"1":"localhost:4001","2":"localhost:4002","3":"zkwasm:4001","4":"wasm:4001"}`,
2830
ChainEndpoint: "https://babel-api.testnet.iotex.io",
2931
DatasourceDSN: "postgres://postgres:mysecretpassword@postgres:5432/w3bstream?sslmode=disable",

cmd/prover/config/config_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func TestConfig_Init(t *testing.T) {
1717
t.Run("UseEnvConfig", func(t *testing.T) {
1818
os.Clearenv()
1919
expected := Config{
20+
ServiceEndpoint: "test",
2021
VMEndpoints: `{"1":"halo2:4001","2":"risc0:4001","3":"zkwasm:4001","4":"wasm:4001"}`,
2122
ChainEndpoint: "http://abc.def.com",
2223
DatasourceDSN: "postgres://root@localhost/abc?ext=666",
@@ -25,6 +26,7 @@ func TestConfig_Init(t *testing.T) {
2526
LocalDBDir: "./test",
2627
}
2728

29+
_ = os.Setenv("HTTP_SERVICE_ENDPOINT", expected.ServiceEndpoint)
2830
_ = os.Setenv("VM_ENDPOINTS", expected.VMEndpoints)
2931
_ = os.Setenv("CHAIN_ENDPOINT", expected.ChainEndpoint)
3032
_ = os.Setenv("DATASOURCE_DSN", expected.DatasourceDSN)

cmd/prover/db/db.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package db
22

33
import (
44
"bytes"
5+
"time"
56

67
"github.com/ethereum/go-ethereum/common"
78
"github.com/pkg/errors"
@@ -35,6 +36,7 @@ type task struct {
3536
TaskID common.Hash `gorm:"uniqueIndex:task_uniq,not null"`
3637
ProjectID uint64 `gorm:"uniqueIndex:task_uniq,not null"`
3738
Processed bool `gorm:"index:unprocessed_task,not null,default:false"`
39+
Error string `gorm:"not null,default:''"`
3840
}
3941

4042
type DB struct {
@@ -128,11 +130,14 @@ func (p *DB) CreateTask(projectID uint64, taskID common.Hash, prover common.Addr
128130
return errors.Wrap(err, "failed to upsert task")
129131
}
130132

131-
func (p *DB) ProcessTask(projectID uint64, taskID common.Hash) error {
133+
func (p *DB) ProcessTask(projectID uint64, taskID common.Hash, err error) error {
132134
t := &task{
133135
Processed: true,
134136
}
135-
err := p.db.Model(t).Where("task_id = ?", taskID).Where("project_id = ?", projectID).Updates(t).Error
137+
if err != nil {
138+
t.Error = err.Error()
139+
}
140+
err = p.db.Model(t).Where("task_id = ?", taskID).Where("project_id = ?", projectID).Updates(t).Error
136141
return errors.Wrap(err, "failed to update task")
137142
}
138143

@@ -141,6 +146,17 @@ func (p *DB) DeleteTask(projectID uint64, taskID, tx common.Hash) error {
141146
return errors.Wrap(err, "failed to delete task")
142147
}
143148

149+
func (p *DB) ProcessedTask(projectID uint64, taskID common.Hash) (bool, string, time.Time, error) {
150+
t := task{}
151+
if err := p.db.Where("task_id = ?", taskID).Where("project_id = ?", projectID).First(&t).Error; err != nil {
152+
if err == gorm.ErrRecordNotFound {
153+
return false, "", time.Now(), nil
154+
}
155+
return false, "", time.Time{}, errors.Wrap(err, "failed to query processed task")
156+
}
157+
return t.Processed, t.Error, t.CreatedAt, nil
158+
}
159+
144160
func (p *DB) UnprocessedTask() (uint64, common.Hash, error) {
145161
t := task{}
146162
if err := p.db.Order("created_at ASC").Where("processed = false").First(&t).Error; err != nil {

cmd/prover/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/ethereum/go-ethereum/crypto"
1313
"github.com/pkg/errors"
1414

15+
"github.com/iotexproject/w3bstream/cmd/prover/api"
1516
"github.com/iotexproject/w3bstream/cmd/prover/config"
1617
"github.com/iotexproject/w3bstream/cmd/prover/db"
1718
"github.com/iotexproject/w3bstream/datasource"
@@ -83,6 +84,12 @@ func main() {
8384
log.Fatal(errors.Wrap(err, "failed to run task processor"))
8485
}
8586

87+
go func() {
88+
if err := api.Run(db, cfg.ServiceEndpoint); err != nil {
89+
log.Fatal(err)
90+
}
91+
}()
92+
8693
done := make(chan os.Signal, 1)
8794
signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
8895
<-done

task/processor/processor.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type RetrieveTask func(projectID uint64, taskID common.Hash) (*task.Task, error)
2525

2626
type DB interface {
2727
UnprocessedTask() (uint64, common.Hash, error)
28-
ProcessTask(uint64, common.Hash) error
28+
ProcessTask(uint64, common.Hash, error) error
2929
}
3030

3131
type processor struct {
@@ -98,12 +98,11 @@ func (r *processor) run() {
9898
time.Sleep(r.waitingTime)
9999
continue
100100
}
101-
if err := r.process(projectID, taskID); err != nil {
101+
err = r.process(projectID, taskID)
102+
if err != nil {
102103
slog.Error("failed to process task", "error", err)
103-
time.Sleep(r.waitingTime)
104-
continue
105104
}
106-
if err := r.db.ProcessTask(projectID, taskID); err != nil {
105+
if err := r.db.ProcessTask(projectID, taskID, err); err != nil {
107106
slog.Error("failed to process db task", "error", err)
108107
}
109108
}

0 commit comments

Comments
 (0)