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