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