mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-09 14:08:03 -05:00
Co-authored-by: georgehao <haohongfan@gmail.com> Co-authored-by: colin <102356659+colinlyguo@users.noreply.github.com> Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
210 lines
5.8 KiB
Go
210 lines
5.8 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/go-resty/resty/v2"
|
|
"github.com/scroll-tech/go-ethereum/log"
|
|
|
|
"scroll-tech/prover/config"
|
|
|
|
"scroll-tech/common/types"
|
|
"scroll-tech/common/types/message"
|
|
"scroll-tech/common/version"
|
|
)
|
|
|
|
// CoordinatorClient is a client used for interacting with the Coordinator service.
|
|
type CoordinatorClient struct {
|
|
client *resty.Client
|
|
|
|
proverName string
|
|
hardForkName string
|
|
priv *ecdsa.PrivateKey
|
|
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewCoordinatorClient constructs a new CoordinatorClient.
|
|
func NewCoordinatorClient(cfg *config.CoordinatorConfig, proverName string, hardForkName string, priv *ecdsa.PrivateKey) (*CoordinatorClient, error) {
|
|
client := resty.New().
|
|
SetTimeout(time.Duration(cfg.ConnectionTimeoutSec) * time.Second).
|
|
SetRetryCount(cfg.RetryCount).
|
|
SetRetryWaitTime(time.Duration(cfg.RetryWaitTimeSec) * time.Second).
|
|
SetBaseURL(cfg.BaseURL).
|
|
AddRetryAfterErrorCondition().
|
|
AddRetryCondition(func(response *resty.Response, err error) bool {
|
|
if err != nil {
|
|
log.Warn("Encountered an error while sending the request. Retrying...", "error", err)
|
|
return true
|
|
}
|
|
return response.IsError()
|
|
})
|
|
|
|
log.Info("successfully initialized prover client",
|
|
"base url", cfg.BaseURL,
|
|
"connection timeout (second)", cfg.ConnectionTimeoutSec,
|
|
"retry count", cfg.RetryCount,
|
|
"retry wait time (second)", cfg.RetryWaitTimeSec)
|
|
|
|
return &CoordinatorClient{
|
|
client: client,
|
|
proverName: proverName,
|
|
hardForkName: hardForkName,
|
|
priv: priv,
|
|
}, nil
|
|
}
|
|
|
|
// Login completes the entire login process in one function call.
|
|
func (c *CoordinatorClient) Login(ctx context.Context) error {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
var challengeResult ChallengeResponse
|
|
|
|
// Get random string
|
|
challengeResp, err := c.client.R().
|
|
SetHeader("Content-Type", "application/json").
|
|
SetResult(&challengeResult).
|
|
Get("/coordinator/v1/challenge")
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("get random string failed: %w", err)
|
|
}
|
|
|
|
if challengeResp.StatusCode() != 200 {
|
|
return fmt.Errorf("failed to get random string, status code: %v", challengeResp.StatusCode())
|
|
}
|
|
|
|
// Prepare and sign the login request
|
|
authMsg := &message.AuthMsg{
|
|
Identity: &message.Identity{
|
|
ProverVersion: version.Version,
|
|
ProverName: c.proverName,
|
|
Challenge: challengeResult.Data.Token,
|
|
HardForkName: c.hardForkName,
|
|
},
|
|
}
|
|
|
|
err = authMsg.SignWithKey(c.priv)
|
|
if err != nil {
|
|
return fmt.Errorf("signature failed: %w", err)
|
|
}
|
|
|
|
// Login to coordinator
|
|
loginReq := &LoginRequest{
|
|
Message: struct {
|
|
Challenge string `json:"challenge"`
|
|
ProverName string `json:"prover_name"`
|
|
ProverVersion string `json:"prover_version"`
|
|
HardForkName string `json:"hard_fork_name"`
|
|
}{
|
|
Challenge: authMsg.Identity.Challenge,
|
|
ProverName: authMsg.Identity.ProverName,
|
|
ProverVersion: authMsg.Identity.ProverVersion,
|
|
HardForkName: authMsg.Identity.HardForkName,
|
|
},
|
|
Signature: authMsg.Signature,
|
|
}
|
|
|
|
// store JWT token for login requests
|
|
c.client.SetAuthToken(challengeResult.Data.Token)
|
|
|
|
var loginResult LoginResponse
|
|
loginResp, err := c.client.R().
|
|
SetHeader("Content-Type", "application/json").
|
|
SetBody(loginReq).
|
|
SetResult(&loginResult).
|
|
Post("/coordinator/v1/login")
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("login failed: %w", err)
|
|
}
|
|
|
|
if loginResp.StatusCode() != 200 {
|
|
return fmt.Errorf("failed to login, status code: %v", loginResp.StatusCode())
|
|
}
|
|
|
|
if loginResult.ErrCode != types.Success {
|
|
return fmt.Errorf("failed to login, error code: %v, error message: %v", loginResult.ErrCode, loginResult.ErrMsg)
|
|
}
|
|
|
|
// store JWT token for future requests
|
|
c.client.SetAuthToken(loginResult.Data.Token)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetTask sends a request to the coordinator to get prover task.
|
|
func (c *CoordinatorClient) GetTask(ctx context.Context, req *GetTaskRequest) (*GetTaskResponse, error) {
|
|
var result GetTaskResponse
|
|
|
|
resp, err := c.client.R().
|
|
SetHeader("Content-Type", "application/json").
|
|
SetBody(req).
|
|
SetResult(&result).
|
|
Post("/coordinator/v1/get_task")
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request for GetTask failed: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode() != 200 {
|
|
return nil, fmt.Errorf("failed to get task, status code: %v", resp.StatusCode())
|
|
}
|
|
|
|
if result.ErrCode == types.ErrJWTTokenExpired {
|
|
log.Info("JWT expired, attempting to re-login")
|
|
if err := c.Login(ctx); err != nil {
|
|
return nil, fmt.Errorf("JWT expired, re-login failed: %w", err)
|
|
}
|
|
log.Info("re-login success")
|
|
return c.GetTask(ctx, req)
|
|
}
|
|
if result.ErrCode != types.Success {
|
|
return nil, fmt.Errorf("error code: %v, error message: %v", result.ErrCode, result.ErrMsg)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// SubmitProof sends a request to the coordinator to submit proof.
|
|
func (c *CoordinatorClient) SubmitProof(ctx context.Context, req *SubmitProofRequest) error {
|
|
var result SubmitProofResponse
|
|
|
|
resp, err := c.client.R().
|
|
SetHeader("Content-Type", "application/json").
|
|
SetBody(req).
|
|
SetResult(&result).
|
|
Post("/coordinator/v1/submit_proof")
|
|
|
|
if err != nil {
|
|
log.Error("submit proof request failed", "error", err)
|
|
return fmt.Errorf("submit proof request failed: %w", ErrCoordinatorConnect)
|
|
}
|
|
|
|
if resp.StatusCode() != 200 {
|
|
log.Error("failed to submit proof", "status code", resp.StatusCode())
|
|
return fmt.Errorf("failed to submit proof, status code not 200: %w", ErrCoordinatorConnect)
|
|
}
|
|
|
|
if result.ErrCode == types.ErrJWTTokenExpired {
|
|
log.Info("JWT expired, attempting to re-login")
|
|
if err := c.Login(ctx); err != nil {
|
|
log.Error("JWT expired, re-login failed", "error", err)
|
|
return fmt.Errorf("JWT expired, re-login failed: %w", ErrCoordinatorConnect)
|
|
}
|
|
log.Info("re-login success")
|
|
return c.SubmitProof(ctx, req)
|
|
}
|
|
|
|
if result.ErrCode != types.Success {
|
|
return fmt.Errorf("error code: %v, error message: %v", result.ErrCode, result.ErrMsg)
|
|
}
|
|
|
|
return nil
|
|
}
|