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