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 tx = nTx.L2Tx()
184 tx.BatchNum = common.BatchNum(currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost
185
186 currBatch.L2Txs = append(currBatch.L2Txs, tx)
187 case common.TxTypeExit:
188 tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
189 tx := common.L2Tx{
190 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
191 ToIdx: common.Idx(1), // as is an Exit
192 Amount: big.NewInt(int64(inst.amount)),
193 Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
194 Type: common.TxTypeExit,
195 }
196 nTx, err := common.NewPoolL2Tx(tx.PoolL2Tx())
197 if err != nil {
198 panic(err)
199 }
200 tx = nTx.L2Tx()
201 currBatch.L2Txs = append(currBatch.L2Txs, tx)
202 case common.TxTypeForceExit:
203 tx := common.L1Tx{
204 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
205 ToIdx: common.Idx(1), // as is an Exit
206 TokenID: inst.tokenID,
207 Amount: big.NewInt(int64(inst.amount)),
208 Type: common.TxTypeExit,
209 }
210 currBatch.L1UserTxs = append(currBatch.L1UserTxs, tx)
211 case typeNewBatch:
212 currBlock.Batches = append(currBlock.Batches, currBatch)
213 currBatchNum++
214 currBatch = BatchData{}
215 case typeNewBlock:
216 currBlock.Batches = append(currBlock.Batches, currBatch)
217 currBatchNum++
218 currBatch = BatchData{}
219 blocks = append(blocks, currBlock)
220 currBlock = BlockData{}
221 default:
222 log.Fatalf("Unexpected type: %s", inst.typ)
223 }
224 }
225 currBlock.Batches = append(currBlock.Batches, currBatch)
226 blocks = append(blocks, currBlock)
227
228 return blocks
229}
230
231// GeneratePoolL2Txs returns an array of common.PoolL2Tx from a given set. It
232// uses the accounts (keys & nonces) of the TestContext.
233func (tc *TestContext) GeneratePoolL2Txs(set string) []common.PoolL2Tx {
234 parser := newParser(strings.NewReader(set))
235 parsedSet, err := parser.parse()
236 require.Nil(tc.t, err)
237
238 tc.Instructions = parsedSet.instructions
239 tc.accountsNames = parsedSet.accounts
240 tc.TokenIDs = parsedSet.tokenIDs
241
242 tc.generateKeys(tc.accountsNames)
243
244 txs := []common.PoolL2Tx{}
245 for _, inst := range tc.Instructions {
246 switch inst.typ {
247 case common.TxTypeTransfer:
248 if tc.Users[inst.from].Accounts[inst.tokenID] == nil {
249 log.Fatalf("Transfer from User %s for TokenID %d while account not created yet", inst.from, inst.tokenID)
250 }
251 if tc.Users[inst.to].Accounts[inst.tokenID] == nil {
252 log.Fatalf("Transfer to User %s for TokenID %d while account not created yet", inst.to, inst.tokenID)
253 }
254 tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
255 // if account of receiver does not exist, don't use
256 // ToIdx, and use only ToEthAddr & ToBJJ
257 tx := common.PoolL2Tx{
258 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
259 ToIdx: tc.Users[inst.to].Accounts[inst.tokenID].Idx,
260 ToEthAddr: tc.Users[inst.to].Addr,
261 ToBJJ: tc.Users[inst.to].BJJ.Public(),
262 TokenID: inst.tokenID,
263 Amount: big.NewInt(int64(inst.amount)),
264 Fee: common.FeeSelector(inst.fee),
265 Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
266 State: common.PoolL2TxStatePending,
267 Timestamp: time.Now(),
268 RqToEthAddr: common.EmptyAddr,
269 RqToBJJ: nil,
270 Type: common.TxTypeTransfer,
271 }
272 nTx, err := common.NewPoolL2Tx(&tx)
273 if err != nil {
274 panic(err)
275 }
276 tx = *nTx
277 // perform signature and set it to tx.Signature
278 toSign, err := tx.HashToSign()
279 if err != nil {
280 panic(err)
281 }
282 sig := tc.Users[inst.to].BJJ.SignPoseidon(toSign)
283 tx.Signature = sig
284
285 txs = append(txs, tx)
286 case common.TxTypeExit:
287 tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
288 tx := common.PoolL2Tx{
289 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
290 ToIdx: common.Idx(1), // as is an Exit
291 TokenID: inst.tokenID,
292 Amount: big.NewInt(int64(inst.amount)),
293 Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
294 Type: common.TxTypeExit,
295 }
296 txs = append(txs, tx)
297 default:
298 log.Fatalf("instruction type unrecognized: %s", inst.typ)
299 }
300 }
301
302 return txs
303}
304
305// generateKeys generates BabyJubJub & Address keys for the given list of
306// account names in a deterministic way. This means, that for the same given
307// 'accNames' in a certain order, the keys will be always the same.
308func (tc *TestContext) generateKeys(accNames []string) {
309 for i := 1; i < len(accNames)+1; i++ {
310 if _, ok := tc.Users[accNames[i-1]]; ok {
311 // account already created
312 continue
313 }
314 // babyjubjub key
315 var sk babyjub.PrivateKey
316 copy(sk[:], []byte(strconv.Itoa(i))) // only for testing
317
318 // eth address
319 var key ecdsa.PrivateKey
320 key.D = big.NewInt(int64(i)) // only for testing
321 key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
322 key.Curve = ethCrypto.S256()
323 addr := ethCrypto.PubkeyToAddress(key.PublicKey)
324
325 u := User{
326 BJJ: &sk,
327 Addr: addr,
328 Accounts: make(map[common.TokenID]*Account),
329 }
330 tc.Users[accNames[i-1]] = &u
331 }
332}