hermez-node/api/txspool_test.go
api/txspool_test.goRaw
1package api
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "math/big"
8 "testing"
9 "time"
10
11 "github.com/hermeznetwork/hermez-node/common"
12 "github.com/hermeznetwork/hermez-node/db/historydb"
13 "github.com/iden3/go-iden3-crypto/babyjub"
14 "github.com/stretchr/testify/assert"
15)
16
17// testPoolTxReceive is a struct to be used to assert the response
18// of GET /transactions-pool/:id
19type testPoolTxReceive struct {
20 TxID common.TxID `json:"id"`
21 Type common.TxType `json:"type"`
22 FromIdx string `json:"fromAccountIndex"`
23 FromEthAddr *string `json:"fromHezEthereumAddress"`
24 FromBJJ *string `json:"fromBJJ"`
25 ToIdx *string `json:"toAccountIndex"`
26 ToEthAddr *string `json:"toHezEthereumAddress"`
27 ToBJJ *string `json:"toBjj"`
28 Amount string `json:"amount"`
29 Fee common.FeeSelector `json:"fee"`
30 Nonce common.Nonce `json:"nonce"`
31 State common.PoolL2TxState `json:"state"`
32 Signature babyjub.SignatureComp `json:"signature"`
33 RqFromIdx *string `json:"requestFromAccountIndex"`
34 RqToIdx *string `json:"requestToAccountIndex"`
35 RqToEthAddr *string `json:"requestToHezEthereumAddress"`
36 RqToBJJ *string `json:"requestToBJJ"`
37 RqTokenID *common.TokenID `json:"requestTokenId"`
38 RqAmount *string `json:"requestAmount"`
39 RqFee *common.FeeSelector `json:"requestFee"`
40 RqNonce *common.Nonce `json:"requestNonce"`
41 BatchNum *common.BatchNum `json:"batchNum"`
42 Timestamp time.Time `json:"timestamp"`
43 Token historydb.TokenWithUSD `json:"token"`
44}
45
46// testPoolTxSend is a struct to be used as a JSON body
47// when testing POST /transactions-pool
48type testPoolTxSend struct {
49 TxID common.TxID `json:"id" binding:"required"`
50 Type common.TxType `json:"type" binding:"required"`
51 TokenID common.TokenID `json:"tokenId"`
52 FromIdx string `json:"fromAccountIndex" binding:"required"`
53 ToIdx *string `json:"toAccountIndex"`
54 ToEthAddr *string `json:"toHezEthereumAddress"`
55 ToBJJ *string `json:"toBjj"`
56 Amount string `json:"amount" binding:"required"`
57 Fee common.FeeSelector `json:"fee"`
58 Nonce common.Nonce `json:"nonce"`
59 Signature babyjub.SignatureComp `json:"signature" binding:"required"`
60 RqFromIdx *string `json:"requestFromAccountIndex"`
61 RqToIdx *string `json:"requestToAccountIndex"`
62 RqToEthAddr *string `json:"requestToHezEthereumAddress"`
63 RqToBJJ *string `json:"requestToBjj"`
64 RqTokenID *common.TokenID `json:"requestTokenId"`
65 RqAmount *string `json:"requestAmount"`
66 RqFee *common.FeeSelector `json:"requestFee"`
67 RqNonce *common.Nonce `json:"requestNonce"`
68}
69
70func genTestPoolTx(accs []common.Account, privKs []babyjub.PrivateKey, tokens []historydb.TokenWithUSD) (poolTxsToSend []testPoolTxSend, poolTxsToReceive []testPoolTxReceive) {
71 // Generate common.PoolL2Tx
72 // WARNING: this should be replaced once til is ready
73 poolTxs := []common.PoolL2Tx{}
74 amount := new(big.Int)
75 amount, ok := amount.SetString("100000000000000", 10)
76 if !ok {
77 panic("bad amount")
78 }
79 poolTx := common.PoolL2Tx{
80 FromIdx: accs[0].Idx,
81 ToIdx: accs[1].Idx,
82 Amount: amount,
83 TokenID: accs[0].TokenID,
84 Nonce: 6,
85 }
86 if _, err := common.NewPoolL2Tx(&poolTx); err != nil {
87 panic(err)
88 }
89 h, err := poolTx.HashToSign()
90 if err != nil {
91 panic(err)
92 }
93 poolTx.Signature = privKs[0].SignPoseidon(h).Compress()
94 poolTxs = append(poolTxs, poolTx)
95 // Transform to API formats
96 poolTxsToSend = []testPoolTxSend{}
97 poolTxsToReceive = []testPoolTxReceive{}
98 for _, poolTx := range poolTxs {
99 fmt.Println(poolTx)
100 // common.PoolL2Tx ==> testPoolTxSend
101 token := getTokenByID(poolTx.TokenID, tokens)
102 genSendTx := testPoolTxSend{
103 TxID: poolTx.TxID,
104 Type: poolTx.Type,
105 TokenID: poolTx.TokenID,
106 FromIdx: idxToHez(poolTx.FromIdx, token.Symbol),
107 Amount: poolTx.Amount.String(),
108 Fee: poolTx.Fee,
109 Nonce: poolTx.Nonce,
110 Signature: poolTx.Signature,
111 RqFee: &poolTx.RqFee,
112 RqNonce: &poolTx.RqNonce,
113 }
114 // common.PoolL2Tx ==> testPoolTxReceive
115 genReceiveTx := testPoolTxReceive{
116 TxID: poolTx.TxID,
117 Type: poolTx.Type,
118 FromIdx: idxToHez(poolTx.FromIdx, token.Symbol),
119 Amount: poolTx.Amount.String(),
120 Fee: poolTx.Fee,
121 Nonce: poolTx.Nonce,
122 State: poolTx.State,
123 Signature: poolTx.Signature,
124 Timestamp: poolTx.Timestamp,
125 // BatchNum: poolTx.BatchNum,
126 RqFee: &poolTx.RqFee,
127 RqNonce: &poolTx.RqNonce,
128 Token: token,
129 }
130 fromAcc := getAccountByIdx(poolTx.ToIdx, accs)
131 fromAddr := ethAddrToHez(fromAcc.EthAddr)
132 genReceiveTx.FromEthAddr = &fromAddr
133 fromBjj := bjjToString(fromAcc.PublicKey)
134 genReceiveTx.FromBJJ = &fromBjj
135 if poolTx.ToIdx != 0 {
136 toIdx := idxToHez(poolTx.ToIdx, token.Symbol)
137 genSendTx.ToIdx = &toIdx
138 genReceiveTx.ToIdx = &toIdx
139 }
140 if poolTx.ToEthAddr != common.EmptyAddr {
141 toEth := ethAddrToHez(poolTx.ToEthAddr)
142 genSendTx.ToEthAddr = &toEth
143 genReceiveTx.ToEthAddr = &toEth
144 } else if poolTx.ToIdx > 255 {
145 acc := getAccountByIdx(poolTx.ToIdx, accs)
146 addr := ethAddrToHez(acc.EthAddr)
147 genReceiveTx.ToEthAddr = &addr
148 }
149 if poolTx.ToBJJ != nil {
150 toBJJ := bjjToString(poolTx.ToBJJ)
151 genSendTx.ToBJJ = &toBJJ
152 genReceiveTx.ToBJJ = &toBJJ
153 } else if poolTx.ToIdx > 255 {
154 acc := getAccountByIdx(poolTx.ToIdx, accs)
155 bjj := bjjToString(acc.PublicKey)
156 genReceiveTx.ToBJJ = &bjj
157 }
158 if poolTx.RqFromIdx != 0 {
159 rqFromIdx := idxToHez(poolTx.RqFromIdx, token.Symbol)
160 genSendTx.RqFromIdx = &rqFromIdx
161 genReceiveTx.RqFromIdx = &rqFromIdx
162 genSendTx.RqTokenID = &token.TokenID
163 genReceiveTx.RqTokenID = &token.TokenID
164 rqAmount := poolTx.RqAmount.String()
165 genSendTx.RqAmount = &rqAmount
166 genReceiveTx.RqAmount = &rqAmount
167
168 if poolTx.RqToIdx != 0 {
169 rqToIdx := idxToHez(poolTx.RqToIdx, token.Symbol)
170 genSendTx.RqToIdx = &rqToIdx
171 genReceiveTx.RqToIdx = &rqToIdx
172 }
173 if poolTx.RqToEthAddr != common.EmptyAddr {
174 rqToEth := ethAddrToHez(poolTx.RqToEthAddr)
175 genSendTx.RqToEthAddr = &rqToEth
176 genReceiveTx.RqToEthAddr = &rqToEth
177 }
178 if poolTx.RqToBJJ != nil {
179 rqToBJJ := bjjToString(poolTx.RqToBJJ)
180 genSendTx.RqToBJJ = &rqToBJJ
181 genReceiveTx.RqToBJJ = &rqToBJJ
182 }
183 }
184 poolTxsToSend = append(poolTxsToSend, genSendTx)
185 poolTxsToReceive = append(poolTxsToReceive, genReceiveTx)
186 }
187 return poolTxsToSend, poolTxsToReceive
188}
189
190func TestPoolTxs(t *testing.T) {
191 // POST
192 endpoint := apiURL + "transactions-pool"
193 fetchedTxID := common.TxID{}
194 for _, tx := range tc.poolTxsToSend {
195 jsonTxBytes, err := json.Marshal(tx)
196 assert.NoError(t, err)
197 jsonTxReader := bytes.NewReader(jsonTxBytes)
198 assert.NoError(
199 t, doGoodReq(
200 "POST",
201 endpoint,
202 jsonTxReader, &fetchedTxID,
203 ),
204 )
205 assert.Equal(t, tx.TxID, fetchedTxID)
206 }
207 // 400
208 // Wrong fee
209 badTx := tc.poolTxsToSend[0]
210 badTx.Amount = "99999999999999999999999"
211 badTx.Fee = 255
212 jsonTxBytes, err := json.Marshal(badTx)
213 assert.NoError(t, err)
214 jsonTxReader := bytes.NewReader(jsonTxBytes)
215 err = doBadReq("POST", endpoint, jsonTxReader, 400)
216 assert.NoError(t, err)
217 // Wrong signature
218 badTx = tc.poolTxsToSend[0]
219 badTx.FromIdx = "hez:foo:1000"
220 jsonTxBytes, err = json.Marshal(badTx)
221 assert.NoError(t, err)
222 jsonTxReader = bytes.NewReader(jsonTxBytes)
223 err = doBadReq("POST", endpoint, jsonTxReader, 400)
224 assert.NoError(t, err)
225 // Wrong to
226 badTx = tc.poolTxsToSend[0]
227 ethAddr := "hez:0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
228 badTx.ToEthAddr = ðAddr
229 badTx.ToIdx = nil
230 jsonTxBytes, err = json.Marshal(badTx)
231 assert.NoError(t, err)
232 jsonTxReader = bytes.NewReader(jsonTxBytes)
233 err = doBadReq("POST", endpoint, jsonTxReader, 400)
234 assert.NoError(t, err)
235 // Wrong rq
236 badTx = tc.poolTxsToSend[0]
237 rqFromIdx := "hez:foo:30"
238 badTx.RqFromIdx = &rqFromIdx
239 jsonTxBytes, err = json.Marshal(badTx)
240 assert.NoError(t, err)
241 jsonTxReader = bytes.NewReader(jsonTxBytes)
242 err = doBadReq("POST", endpoint, jsonTxReader, 400)
243 assert.NoError(t, err)
244 // GET
245 endpoint += "/"
246 for _, tx := range tc.poolTxsToReceive {
247 fetchedTx := testPoolTxReceive{}
248 assert.NoError(
249 t, doGoodReq(
250 "GET",
251 endpoint+tx.TxID.String(),
252 nil, &fetchedTx,
253 ),
254 )
255 assertPoolTx(t, tx, fetchedTx)
256 }
257 // 400
258 err = doBadReq("GET", endpoint+"0xG20000000156660000000090", nil, 400)
259 assert.NoError(t, err)
260 // 404
261 err = doBadReq("GET", endpoint+"0x020000000156660000000090", nil, 404)
262 assert.NoError(t, err)
263}
264
265func assertPoolTx(t *testing.T, expected, actual testPoolTxReceive) {
266 // state should be pending
267 assert.Equal(t, common.PoolL2TxStatePending, actual.State)
268 expected.State = actual.State
269 actual.Token.ItemID = 0
270 // timestamp should be very close to now
271 assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
272 expected.Timestamp = actual.Timestamp
273 // token timestamp
274 if expected.Token.USDUpdate == nil {
275 assert.Equal(t, expected.Token.USDUpdate, actual.Token.USDUpdate)
276 } else {
277 assert.Equal(t, expected.Token.USDUpdate.Unix(), actual.Token.USDUpdate.Unix())
278 expected.Token.USDUpdate = actual.Token.USDUpdate
279 }
280 assert.Equal(t, expected, actual)
281}