forked from taikoxyz/taiko-client
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapi.go
285 lines (252 loc) · 8.69 KB
/
api.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package server
import (
"context"
"math/big"
"net/http"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/labstack/echo/v4"
"github.com/taikoxyz/taiko-client/bindings/encoding"
"github.com/taikoxyz/taiko-client/internal/utils"
"github.com/taikoxyz/taiko-client/pkg/rpc"
)
const (
rpcTimeout = 1 * time.Minute
)
// @title Taiko Prover Server API
// @version 1.0
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url https://community.taiko.xyz/
// @contact.email info@taiko.xyz
// @license.name MIT
// @license.url https://github.com/taikoxyz/taiko-client/blob/main/LICENSE.md
// CreateAssignmentRequestBody represents a request body when handling assignment creation request.
type CreateAssignmentRequestBody struct {
Proposer common.Address
FeeToken common.Address
TierFees []encoding.TierFee
Expiry uint64
BlobHash common.Hash
}
// Status represents the current prover server status.
type Status struct {
MinOptimisticTierFee uint64 `json:"minOptimisticTierFee"`
MinSgxTierFee uint64 `json:"minSgxTierFee"`
MinSgxAndZkVMTierFee uint64 `json:"minSgxAndZkVMTierFee"`
MaxExpiry uint64 `json:"maxExpiry"`
Prover string `json:"prover"`
}
// GetStatus handles a query to the current prover server status.
//
// @Summary Get current prover server status
// @ID get-status
// @Accept json
// @Produce json
// @Success 200 {object} Status
// @Router /status [get]
func (s *ProverServer) GetStatus(c echo.Context) error {
return c.JSON(http.StatusOK, &Status{
MinOptimisticTierFee: s.minOptimisticTierFee.Uint64(),
MinSgxTierFee: s.minSgxTierFee.Uint64(),
MinSgxAndZkVMTierFee: s.minSgxAndZkVMTierFee.Uint64(),
MaxExpiry: uint64(s.maxExpiry.Seconds()),
Prover: s.proverAddress.Hex(),
})
}
// ProposeBlockResponse represents the JSON response which will be returned by
// the ProposeBlock request handler.
type ProposeBlockResponse struct {
SignedPayload []byte `json:"signedPayload"`
Prover common.Address `json:"prover"`
MaxBlockID uint64 `json:"maxBlockID"`
MaxProposedIn uint64 `json:"maxProposedIn"`
}
// CreateAssignment handles a block proof assignment request, decides if this prover wants to
// handle this block, and if so, returns a signed payload the proposer
// can submit onchain.
//
// @Summary Try to accept a block proof assignment
// @Param body CreateAssignmentRequestBody true "assignment request body"
// @Accept json
// @Produce json
// @Success 200 {object} ProposeBlockResponse
// @Failure 422 {string} string "empty blob hash"
// @Failure 422 {string} string "only receive ETH"
// @Failure 422 {string} string "insufficient prover balance"
// @Failure 422 {string} string "proof fee too low"
// @Failure 422 {string} string "expiry too long"
// @Failure 422 {string} string "prover does not have capacity"
// @Router /assignment [post]
func (s *ProverServer) CreateAssignment(c echo.Context) error {
req := new(CreateAssignmentRequestBody)
if err := c.Bind(req); err != nil {
return c.JSON(http.StatusUnprocessableEntity, err)
}
log.Info(
"Proof assignment request body",
"feeToken", req.FeeToken,
"expiry", req.Expiry,
"tierFees", req.TierFees,
"blobHash", req.BlobHash,
"currentUsedCapacity", len(s.proofSubmissionCh),
)
// 1. Check if the request body is valid.
if req.BlobHash == (common.Hash{}) {
log.Warn("Empty blob hash", "prover", s.proverAddress)
return echo.NewHTTPError(http.StatusUnprocessableEntity, "empty blob hash")
}
if req.FeeToken != (common.Address{}) {
log.Warn("Only receive ETH", "prover", s.proverAddress)
return echo.NewHTTPError(http.StatusUnprocessableEntity, "only receive ETH")
}
// 2. Check if the prover has the required minimum on-chain ETH and Taiko token balance.
ok, err := s.checkMinEthAndToken(c.Request().Context())
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
if !ok {
return echo.NewHTTPError(http.StatusUnprocessableEntity, "insufficient prover balance")
}
// 3. Check if the prover's token balance is enough to cover the bonds.
if ok, err = rpc.CheckProverBalance(
c.Request().Context(),
s.rpc,
s.proverAddress,
s.assignmentHookAddress,
s.livenessBond,
); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
if !ok {
log.Warn(
"Insufficient prover token balance, please get more tokens or wait for verification of the blocks you proved",
"prover", s.proverAddress,
)
return echo.NewHTTPError(http.StatusUnprocessableEntity, "insufficient prover balance")
}
// 4. Check if the proof fee meets prover's minimum requirement for each tier.
for _, tier := range req.TierFees {
if tier.Tier == encoding.TierGuardianID {
continue
}
var minTierFee *big.Int
switch tier.Tier {
case encoding.TierOptimisticID:
minTierFee = s.minOptimisticTierFee
case encoding.TierSgxID:
minTierFee = s.minSgxTierFee
case encoding.TierSgxAndZkVMID:
minTierFee = s.minSgxAndZkVMTierFee
default:
log.Warn("Unknown tier", "tier", tier.Tier, "fee", tier.Fee, "proposerIP", c.RealIP())
return echo.NewHTTPError(http.StatusUnprocessableEntity, "unknown tier")
}
if tier.Fee.Cmp(minTierFee) < 0 {
log.Warn(
"Proof fee too low",
"tier", tier.Tier,
"fee", tier.Fee,
"minTierFee", minTierFee,
"proposerIP", c.RealIP(),
)
return echo.NewHTTPError(http.StatusUnprocessableEntity, "proof fee too low")
}
}
// 5. Check if the expiry is too long.
if req.Expiry > uint64(time.Now().Add(s.maxExpiry).Unix()) {
log.Warn(
"Expiry too long",
"requestExpiry", req.Expiry,
"srvMaxExpiry", s.maxExpiry,
"proposerIP", c.RealIP(),
)
return echo.NewHTTPError(http.StatusUnprocessableEntity, "expiry too long")
}
// 6. Check if the prover has any capacity now.
if s.proofSubmissionCh != nil && len(s.proofSubmissionCh) == cap(s.proofSubmissionCh) {
log.Warn("Prover does not have capacity", "capacity", cap(s.proofSubmissionCh))
return echo.NewHTTPError(http.StatusUnprocessableEntity, "prover does not have capacity")
}
// 7. Encode and sign the prover assignment payload.
l1Head, err := s.rpc.L1.BlockNumber(c.Request().Context())
if err != nil {
log.Error("Failed to get L1 block head", "error", err)
return echo.NewHTTPError(http.StatusUnprocessableEntity, err)
}
encoded, err := encoding.EncodeProverAssignmentPayload(
s.protocolConfigs.ChainId,
s.taikoL1Address,
s.assignmentHookAddress,
req.Proposer,
s.proverAddress,
req.BlobHash,
req.FeeToken,
req.Expiry,
l1Head+s.maxSlippage,
s.maxProposedIn,
req.TierFees,
)
if err != nil {
log.Error("Failed to encode proverAssignment payload data", "error", err)
return echo.NewHTTPError(http.StatusUnprocessableEntity, err)
}
signed, err := crypto.Sign(crypto.Keccak256Hash(encoded).Bytes(), s.proverPrivateKey)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err)
}
// 8. Return the signed payload.
return c.JSON(http.StatusOK, &ProposeBlockResponse{
SignedPayload: signed,
Prover: s.proverAddress,
MaxBlockID: l1Head + s.maxSlippage,
MaxProposedIn: s.maxProposedIn,
})
}
// checkMinEthAndToken checks if the prover has the required minimum on-chain ETH and Taiko token balance.
func (s *ProverServer) checkMinEthAndToken(ctx context.Context) (bool, error) {
ctx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
// 1. Check prover's ETH balance.
ethBalance, err := s.rpc.L1.BalanceAt(ctx, s.proverAddress, nil)
if err != nil {
return false, err
}
log.Info(
"Prover's ETH balance",
"balance", utils.WeiToEther(ethBalance),
"address", s.proverAddress.Hex(),
)
if ethBalance.Cmp(s.minEthBalance) <= 0 {
log.Warn(
"Prover does not have required minimum on-chain ETH balance",
"providedProver", s.proverAddress.Hex(),
"ethBalance", utils.WeiToEther(ethBalance),
"minEthBalance", utils.WeiToEther(s.minEthBalance),
)
return false, nil
}
// 2. Check prover's Taiko token balance.
balance, err := s.rpc.TaikoToken.BalanceOf(&bind.CallOpts{Context: ctx}, s.proverAddress)
if err != nil {
return false, err
}
log.Info(
"Prover's Taiko token balance",
"balance", utils.WeiToEther(balance),
"address", s.proverAddress.Hex(),
)
if balance.Cmp(s.minTaikoTokenBalance) <= 0 {
log.Warn(
"Prover does not have required on-chain Taiko token balance",
"providedProver", s.proverAddress.Hex(),
"taikoTokenBalance", utils.WeiToEther(balance),
"minTaikoTokenBalance", utils.WeiToEther(s.minTaikoTokenBalance),
)
return false, nil
}
return true, nil
}