hermez-node/test/txs.go
test/txs.goRaw
1package test
2
3import (
4 "crypto/ecdsa"
5 "math/big"
6 "strconv"
7 "strings"
8 "testing"
9 "time"
10
11 ethCommon "github.com/ethereum/go-ethereum/common"
12 ethCrypto "github.com/ethereum/go-ethereum/crypto"
13 "github.com/hermeznetwork/hermez-node/common"
14 "github.com/hermeznetwork/hermez-node/log"
15 "github.com/iden3/go-iden3-crypto/babyjub"
16 "github.com/stretchr/testify/require"
17)
18
19// TestContext contains the data of the test
20type TestContext struct {
21 t *testing.T
22 Instructions []Instruction
23 accountsNames []string
24 accounts map[string]*Account
25 TokenIDs []common.TokenID
26 l1CreatedAccounts map[string]*Account
27}
28
29// NewTestContext returns a new TestContext
30func NewTestContext(t *testing.T) *TestContext {
31 return &TestContext{
32 t: t,
33 accounts: make(map[string]*Account),
34 l1CreatedAccounts: make(map[string]*Account),
35 }
36}
37
38// Account contains the data related to a testing account
39type Account struct {
40 BJJ *babyjub.PrivateKey
41 Addr ethCommon.Address
42 Idx common.Idx
43 Nonce common.Nonce
44}
45
46// BlockData contains the information of a Block
47type BlockData struct {
48 block *common.Block // ethereum block
49 // L1UserTxs that were submitted in the block
50 L1UserTxs []common.L1Tx
51 Batches []BatchData
52 RegisteredTokens []common.Token
53}
54
55// BatchData contains the information of a Batch
56type BatchData struct {
57 L1Batch bool // TODO: Remove once Batch.ForgeL1TxsNum is a pointer
58 // L1UserTxs that were forged in the batch
59 L1UserTxs []common.L1Tx
60 L1CoordinatorTxs []common.L1Tx
61 L2Txs []common.L2Tx
62 CreatedAccounts []common.Account
63 ExitTree []common.ExitInfo
64 Batch *common.Batch
65}
66
67// GenerateBlocks returns an array of BlockData for a given set. It uses the
68// accounts (keys & nonces) of the TestContext.
69func (tc *TestContext) GenerateBlocks(set string) []BlockData {
70 parser := NewParser(strings.NewReader(set))
71 parsedSet, err := parser.Parse(false)
72 require.Nil(tc.t, err)
73
74 tc.Instructions = parsedSet.Instructions
75 tc.accountsNames = parsedSet.Accounts
76 tc.TokenIDs = parsedSet.TokenIDs
77
78 tc.generateKeys(tc.accountsNames)
79
80 var blocks []BlockData
81 currBatchNum := 0
82 var currBlock BlockData
83 var currBatch BatchData
84 idx := 256
85 for _, inst := range parsedSet.Instructions {
86 switch inst.Type {
87 case common.TxTypeCreateAccountDeposit, common.TxTypeCreateAccountDepositTransfer:
88 tx := common.L1Tx{
89 // TxID
90 FromEthAddr: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Addr,
91 FromBJJ: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].BJJ.Public(),
92 TokenID: inst.TokenID,
93 LoadAmount: big.NewInt(int64(inst.LoadAmount)),
94 Type: inst.Type,
95 }
96 if tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx == common.Idx(0) { // if account.Idx is not set yet, set it and increment idx
97 tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx = common.Idx(idx)
98
99 tc.l1CreatedAccounts[idxTokenIDToString(inst.From, inst.TokenID)] = tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)]
100 idx++
101 }
102 if inst.Type == common.TxTypeCreateAccountDepositTransfer {
103 tx.Amount = big.NewInt(int64(inst.Amount))
104 }
105 currBatch.L1UserTxs = append(currBatch.L1UserTxs, tx)
106 case common.TxTypeDeposit, common.TxTypeDepositTransfer:
107 tx := common.L1Tx{
108 // TxID
109 FromIdx: &tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx,
110 FromEthAddr: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Addr,
111 FromBJJ: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].BJJ.Public(),
112 TokenID: inst.TokenID,
113 LoadAmount: big.NewInt(int64(inst.LoadAmount)),
114 Type: inst.Type,
115 }
116 if tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx == common.Idx(0) {
117 // if account.Idx is not set yet, set it and increment idx
118 tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx = common.Idx(idx)
119
120 tc.l1CreatedAccounts[idxTokenIDToString(inst.From, inst.TokenID)] = tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)]
121 idx++
122 }
123 if inst.Type == common.TxTypeDepositTransfer {
124 tx.Amount = big.NewInt(int64(inst.Amount))
125 // if ToIdx is not set yet, set it and increment idx
126 if tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Idx == common.Idx(0) {
127 tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Idx = common.Idx(idx)
128
129 tc.l1CreatedAccounts[idxTokenIDToString(inst.To, inst.TokenID)] = tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)]
130 tx.ToIdx = common.Idx(idx)
131 idx++
132 } else {
133 // if Idx account of To already exist, use it for ToIdx
134 tx.ToIdx = tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Idx
135 }
136 }
137 currBatch.L1UserTxs = append(currBatch.L1UserTxs, tx)
138 case common.TxTypeTransfer:
139 tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce++
140 // if account of receiver does not exist, create a new CoordinatorL1Tx creating the account
141 if _, ok := tc.l1CreatedAccounts[idxTokenIDToString(inst.To, inst.TokenID)]; !ok {
142 tx := common.L1Tx{
143 FromEthAddr: tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Addr,
144 FromBJJ: tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].BJJ.Public(),
145 TokenID: inst.TokenID,
146 LoadAmount: big.NewInt(int64(inst.Amount)),
147 Type: common.TxTypeCreateAccountDeposit,
148 }
149 tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Idx = common.Idx(idx)
150 tc.l1CreatedAccounts[idxTokenIDToString(inst.To, inst.TokenID)] = tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)]
151 currBatch.L1CoordinatorTxs = append(currBatch.L1CoordinatorTxs, tx)
152 idx++
153 }
154 toEthAddr := new(ethCommon.Address)
155 *toEthAddr = tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Addr
156 rqToEthAddr := new(ethCommon.Address)
157 *rqToEthAddr = tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Addr
158 tx := common.L2Tx{
159 FromIdx: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx,
160 ToIdx: tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Idx,
161 Amount: big.NewInt(int64(inst.Amount)),
162 Fee: common.FeeSelector(inst.Fee),
163 Nonce: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce,
164 BatchNum: common.BatchNum(currBatchNum),
165 Type: common.TxTypeTransfer,
166 }
167 nTx, err := common.NewPoolL2Tx(tx.PoolL2Tx())
168 if err != nil {
169 panic(err)
170 }
171 nL2Tx, err := nTx.L2Tx()
172 if err != nil {
173 panic(err)
174 }
175 tx = *nL2Tx
176
177 currBatch.L2Txs = append(currBatch.L2Txs, tx)
178 case common.TxTypeExit:
179 tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce++
180 tx := common.L2Tx{
181 FromIdx: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx,
182 ToIdx: common.Idx(1), // as is an Exit
183 Amount: big.NewInt(int64(inst.Amount)),
184 Nonce: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce,
185 Type: common.TxTypeExit,
186 }
187 nTx, err := common.NewPoolL2Tx(tx.PoolL2Tx())
188 if err != nil {
189 panic(err)
190 }
191 nL2Tx, err := nTx.L2Tx()
192 if err != nil {
193 panic(err)
194 }
195 tx = *nL2Tx
196 currBatch.L2Txs = append(currBatch.L2Txs, tx)
197 case common.TxTypeForceExit:
198 fromIdx := new(common.Idx)
199 *fromIdx = tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx
200 tx := common.L1Tx{
201 FromIdx: fromIdx,
202 ToIdx: common.Idx(1), // as is an Exit
203 TokenID: inst.TokenID,
204 Amount: big.NewInt(int64(inst.Amount)),
205 Type: common.TxTypeExit,
206 }
207 currBatch.L1UserTxs = append(currBatch.L1UserTxs, tx)
208 case TypeNewBatch:
209 currBlock.Batches = append(currBlock.Batches, currBatch)
210 currBatchNum++
211 currBatch = BatchData{}
212 case TypeNewBlock:
213 currBlock.Batches = append(currBlock.Batches, currBatch)
214 currBatchNum++
215 currBatch = BatchData{}
216 blocks = append(blocks, currBlock)
217 currBlock = BlockData{}
218 default:
219 log.Fatalf("Unexpected type: %s", inst.Type)
220 }
221 }
222 currBlock.Batches = append(currBlock.Batches, currBatch)
223 blocks = append(blocks, currBlock)
224
225 return blocks
226}
227
228// GeneratePoolL2Txs returns an array of common.PoolL2Tx from a given set. It
229// uses the accounts (keys & nonces) of the TestContext.
230func (tc *TestContext) GeneratePoolL2Txs(set string) []common.PoolL2Tx {
231 parser := NewParser(strings.NewReader(set))
232 parsedSet, err := parser.Parse(true)
233 require.Nil(tc.t, err)
234
235 tc.Instructions = parsedSet.Instructions
236 tc.accountsNames = parsedSet.Accounts
237 tc.TokenIDs = parsedSet.TokenIDs
238
239 tc.generateKeys(tc.accountsNames)
240
241 txs := []common.PoolL2Tx{}
242 for _, inst := range tc.Instructions {
243 switch inst.Type {
244 case common.TxTypeTransfer:
245 tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce++
246 // if account of receiver does not exist, don't use
247 // ToIdx, and use only ToEthAddr & ToBJJ
248 toIdx := new(common.Idx)
249 if _, ok := tc.l1CreatedAccounts[idxTokenIDToString(inst.To, inst.TokenID)]; !ok {
250 *toIdx = 0
251 } else {
252 *toIdx = tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Idx
253 }
254 // TODO once common.L{x}Txs parameter pointers is undone, update this lines related to pointers usage
255 toEthAddr := new(ethCommon.Address)
256 *toEthAddr = tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Addr
257 rqToEthAddr := new(ethCommon.Address)
258 *rqToEthAddr = tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Addr
259 tx := common.PoolL2Tx{
260 FromIdx: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx,
261 ToIdx: toIdx,
262 ToEthAddr: toEthAddr,
263 ToBJJ: tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].BJJ.Public(),
264 TokenID: inst.TokenID,
265 Amount: big.NewInt(int64(inst.Amount)),
266 Fee: common.FeeSelector(inst.Fee),
267 Nonce: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce,
268 State: common.PoolL2TxStatePending,
269 Timestamp: time.Now(),
270 BatchNum: nil,
271 RqToEthAddr: rqToEthAddr,
272 RqToBJJ: tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].BJJ.Public(),
273 Type: common.TxTypeTransfer,
274 }
275 nTx, err := common.NewPoolL2Tx(&tx)
276 if err != nil {
277 panic(err)
278 }
279 tx = *nTx
280 // perform signature and set it to tx.Signature
281 toSign, err := tx.HashToSign()
282 if err != nil {
283 panic(err)
284 }
285 sig := tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].BJJ.SignPoseidon(toSign)
286 tx.Signature = sig
287
288 txs = append(txs, tx)
289 case common.TxTypeExit:
290 tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce++
291 // TODO once common.L{x}Txs parameter pointers is undone, update this lines related to pointers usage
292 toIdx := new(common.Idx)
293 *toIdx = common.Idx(1) // as is an Exit
294 tx := common.PoolL2Tx{
295 FromIdx: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx,
296 ToIdx: toIdx, // as is an Exit
297 TokenID: inst.TokenID,
298 Amount: big.NewInt(int64(inst.Amount)),
299 Nonce: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce,
300 Type: common.TxTypeExit,
301 }
302 txs = append(txs, tx)
303 default:
304 log.Fatalf("instruction type unrecognized: %s", inst.Type)
305 }
306 }
307
308 return txs
309}
310
311// generateKeys generates BabyJubJub & Address keys for the given list of
312// account names in a deterministic way. This means, that for the same given
313// 'accNames' in a certain order, the keys will be always the same.
314func (tc *TestContext) generateKeys(accNames []string) map[string]*Account {
315 acc := make(map[string]*Account)
316 for i := 1; i < len(accNames)+1; i++ {
317 if _, ok := tc.accounts[accNames[i-1]]; ok {
318 // account already created
319 continue
320 }
321 // babyjubjub key
322 var sk babyjub.PrivateKey
323 copy(sk[:], []byte(strconv.Itoa(i))) // only for testing
324
325 // eth address
326 var key ecdsa.PrivateKey
327 key.D = big.NewInt(int64(i)) // only for testing
328 key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
329 key.Curve = ethCrypto.S256()
330 addr := ethCrypto.PubkeyToAddress(key.PublicKey)
331
332 a := Account{
333 BJJ: &sk,
334 Addr: addr,
335 Nonce: 0,
336 }
337 tc.accounts[accNames[i-1]] = &a
338 }
339 return acc
340}