hermez-node/api/exits_test.go
api/exits_test.goRaw
1package api
2
3import (
4 "fmt"
5 "testing"
6
7 "github.com/hermeznetwork/hermez-node/apitypes"
8 "github.com/hermeznetwork/hermez-node/common"
9 "github.com/hermeznetwork/hermez-node/db/historydb"
10 "github.com/mitchellh/copystructure"
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13)
14
15type testCVP struct {
16 Root string
17 Siblings []string
18 OldKey string
19 OldValue string
20 IsOld0 bool
21 Key string
22 Value string
23 Fnc int
24}
25
26type testExit struct {
27 ItemID uint64 `json:"itemId"`
28 BatchNum common.BatchNum `json:"batchNum"`
29 AccountIdx string `json:"accountIndex"`
30 BJJ apitypes.HezBJJ `json:"bjj"`
31 EthAddr apitypes.HezEthAddr `json:"hezEthereumAddress"`
32 MerkleProof testCVP `json:"merkleProof"`
33 Balance string `json:"balance"`
34 InstantWithdrawn *int64 `json:"instantWithdrawn"`
35 DelayedWithdrawRequest *int64 `json:"delayedWithdrawRequest"`
36 DelayedWithdrawn *int64 `json:"delayedWithdrawn"`
37 Token historydb.TokenWithUSD `json:"token"`
38}
39
40type testExitsResponse struct {
41 Exits []testExit `json:"exits"`
42 PendingItems uint64 `json:"pendingItems"`
43}
44
45func (t testExitsResponse) GetPending() (pendingItems, lastItemID uint64) {
46 pendingItems = t.PendingItems
47 lastItemID = t.Exits[len(t.Exits)-1].ItemID
48 return pendingItems, lastItemID
49}
50
51func (t testExitsResponse) New() Pendinger { return &testExitsResponse{} }
52
53func (t *testExitsResponse) Len() int {
54 return len(t.Exits)
55}
56
57func genTestExits(
58 commonExits []common.ExitInfo,
59 tokens []historydb.TokenWithUSD,
60 accs []common.Account,
61) []testExit {
62 allExits := []testExit{}
63 for _, exit := range commonExits {
64 token := getTokenByIdx(exit.AccountIdx, tokens, accs)
65 siblings := []string{}
66 for i := 0; i < len(exit.MerkleProof.Siblings); i++ {
67 siblings = append(siblings, exit.MerkleProof.Siblings[i].String())
68 }
69 acc := getAccountByIdx(exit.AccountIdx, accs)
70 allExits = append(allExits, testExit{
71 BatchNum: exit.BatchNum,
72 AccountIdx: idxToHez(exit.AccountIdx, token.Symbol),
73 BJJ: apitypes.NewHezBJJ(acc.PublicKey),
74 EthAddr: apitypes.NewHezEthAddr(acc.EthAddr),
75 MerkleProof: testCVP{
76 Root: exit.MerkleProof.Root.String(),
77 Siblings: siblings,
78 OldKey: exit.MerkleProof.OldKey.String(),
79 OldValue: exit.MerkleProof.OldValue.String(),
80 IsOld0: exit.MerkleProof.IsOld0,
81 Key: exit.MerkleProof.Key.String(),
82 Value: exit.MerkleProof.Value.String(),
83 Fnc: exit.MerkleProof.Fnc,
84 },
85 Balance: exit.Balance.String(),
86 InstantWithdrawn: exit.InstantWithdrawn,
87 DelayedWithdrawRequest: exit.DelayedWithdrawRequest,
88 DelayedWithdrawn: exit.DelayedWithdrawn,
89 Token: token,
90 })
91 }
92 return allExits
93}
94
95func TestGetExits(t *testing.T) {
96 endpoint := apiURL + "exits"
97 fetchedExits := []testExit{}
98 appendIter := func(intr interface{}) {
99 for i := 0; i < len(intr.(*testExitsResponse).Exits); i++ {
100 tmp, err := copystructure.Copy(intr.(*testExitsResponse).Exits[i])
101 if err != nil {
102 panic(err)
103 }
104 fetchedExits = append(fetchedExits, tmp.(testExit))
105 }
106 }
107 // Get all (no filters)
108 limit := 8
109 path := fmt.Sprintf("%s?limit=%d", endpoint, limit)
110 err := doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
111 assert.NoError(t, err)
112 assertExitAPIs(t, tc.exits, fetchedExits)
113
114 // Get by ethAddr
115 fetchedExits = []testExit{}
116 limit = 7
117 var account testAccount
118 for _, tx := range tc.txs {
119 found := false
120 if tx.Type == common.TxTypeExit {
121 for i := 0; i < len(tc.accounts); i++ {
122 if tx.FromIdx != nil && string(tc.accounts[i].Idx) == *tx.FromIdx {
123 account = tc.accounts[i]
124 break
125 }
126 }
127 }
128 if found {
129 break
130 }
131 }
132 path = fmt.Sprintf(
133 "%s?hezEthereumAddress=%s&limit=%d",
134 endpoint, account.EthAddr, limit,
135 )
136 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
137 assert.NoError(t, err)
138 var accountExits []testExit
139 for i := range tc.exits {
140 for _, acc := range tc.accounts {
141 if string(acc.Idx) == tc.exits[i].AccountIdx {
142 if acc.EthAddr == account.EthAddr {
143 accountExits = append(accountExits, tc.exits[i])
144 }
145 }
146 }
147 }
148 assertExitAPIs(t, accountExits, fetchedExits)
149 // Get by bjj
150 fetchedExits = []testExit{}
151 limit = 6
152 path = fmt.Sprintf(
153 "%s?BJJ=%s&limit=%d",
154 endpoint, account.PublicKey, limit,
155 )
156 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
157 assert.NoError(t, err)
158 assertExitAPIs(t, accountExits, fetchedExits)
159 // Get by tokenID
160 fetchedExits = []testExit{}
161 limit = 5
162 tokenID := tc.exits[0].Token.TokenID
163 path = fmt.Sprintf(
164 "%s?tokenId=%d&limit=%d",
165 endpoint, tokenID, limit,
166 )
167 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
168 assert.NoError(t, err)
169 tokenIDExits := []testExit{}
170 for i := 0; i < len(tc.exits); i++ {
171 if tc.exits[i].Token.TokenID == tokenID {
172 tokenIDExits = append(tokenIDExits, tc.exits[i])
173 }
174 }
175 assertExitAPIs(t, tokenIDExits, fetchedExits)
176 // idx
177 fetchedExits = []testExit{}
178 limit = 4
179 idx := tc.exits[0].AccountIdx
180 path = fmt.Sprintf(
181 "%s?accountIndex=%s&limit=%d",
182 endpoint, idx, limit,
183 )
184 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
185 assert.NoError(t, err)
186 idxExits := []testExit{}
187 for i := 0; i < len(tc.exits); i++ {
188 if tc.exits[i].AccountIdx[6:] == idx[6:] {
189 idxExits = append(idxExits, tc.exits[i])
190 }
191 }
192 assertExitAPIs(t, idxExits, fetchedExits)
193 // batchNum
194 fetchedExits = []testExit{}
195 limit = 3
196 batchNum := tc.exits[0].BatchNum
197 path = fmt.Sprintf(
198 "%s?batchNum=%d&limit=%d",
199 endpoint, batchNum, limit,
200 )
201 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
202 assert.NoError(t, err)
203 batchNumExits := []testExit{}
204 for i := 0; i < len(tc.exits); i++ {
205 if tc.exits[i].BatchNum == batchNum {
206 batchNumExits = append(batchNumExits, tc.exits[i])
207 }
208 }
209 assertExitAPIs(t, batchNumExits, fetchedExits)
210 // OnlyPendingWithdraws
211 fetchedExits = []testExit{}
212 limit = 7
213 path = fmt.Sprintf(
214 "%s?&onlyPendingWithdraws=%t&limit=%d",
215 endpoint, true, limit,
216 )
217 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
218 assert.NoError(t, err)
219 pendingExits := []testExit{}
220 for i := 0; i < len(tc.exits); i++ {
221 if tc.exits[i].InstantWithdrawn == nil && tc.exits[i].DelayedWithdrawn == nil {
222 pendingExits = append(pendingExits, tc.exits[i])
223 }
224 }
225 assertExitAPIs(t, pendingExits, fetchedExits)
226 // Multiple filters
227 fetchedExits = []testExit{}
228 limit = 1
229 path = fmt.Sprintf(
230 "%s?batchNum=%d&tokeId=%d&limit=%d",
231 endpoint, batchNum, tokenID, limit,
232 )
233 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
234 assert.NoError(t, err)
235 mixedExits := []testExit{}
236 flipedExits := []testExit{}
237 for i := 0; i < len(tc.exits); i++ {
238 if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID {
239 mixedExits = append(mixedExits, tc.exits[i])
240 }
241 flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i])
242 }
243 assertExitAPIs(t, mixedExits, fetchedExits)
244 // All, in reverse order
245 fetchedExits = []testExit{}
246 limit = 5
247 path = fmt.Sprintf("%s?limit=%d", endpoint, limit)
248 err = doGoodReqPaginated(path, historydb.OrderDesc, &testExitsResponse{}, appendIter)
249 assert.NoError(t, err)
250 assertExitAPIs(t, flipedExits, fetchedExits)
251 // 400
252 path = fmt.Sprintf(
253 "%s?accountIndex=%s&hezEthereumAddress=%s",
254 endpoint, idx, account.EthAddr,
255 )
256 err = doBadReq("GET", path, nil, 400)
257 assert.NoError(t, err)
258 path = fmt.Sprintf("%s?tokenId=X", endpoint)
259 err = doBadReq("GET", path, nil, 400)
260 assert.NoError(t, err)
261 // 404
262 path = fmt.Sprintf("%s?batchNum=999999", endpoint)
263 err = doBadReq("GET", path, nil, 404)
264 assert.NoError(t, err)
265 path = fmt.Sprintf("%s?fromItem=1000999999", endpoint)
266 err = doBadReq("GET", path, nil, 404)
267 assert.NoError(t, err)
268}
269
270func TestGetExit(t *testing.T) {
271 // Get all txs by their ID
272 endpoint := apiURL + "exits/"
273 fetchedExits := []testExit{}
274 for _, exit := range tc.exits {
275 fetchedExit := testExit{}
276 assert.NoError(
277 t, doGoodReq(
278 "GET",
279 fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx),
280 nil, &fetchedExit,
281 ),
282 )
283 fetchedExits = append(fetchedExits, fetchedExit)
284 }
285 assertExitAPIs(t, tc.exits, fetchedExits)
286 // 400
287 err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400)
288 assert.NoError(t, err)
289 err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400)
290 assert.NoError(t, err)
291 // 404
292 err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404)
293 assert.NoError(t, err)
294}
295
296func assertExitAPIs(t *testing.T, expected, actual []testExit) {
297 require.Equal(t, len(expected), len(actual))
298 for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
299 actual[i].ItemID = 0
300 actual[i].Token.ItemID = 0
301 if expected[i].Token.USDUpdate == nil {
302 assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
303 } else {
304 assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
305 expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
306 }
307 assert.Equal(t, expected[i], actual[i])
308 }
309}