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