hermez-node/test/transakcio/txs.go
test/transakcio/txs.goRaw
1package transakcio
2
3import (
4 "crypto/ecdsa"
5 "fmt"
6 "math/big"
7 "strconv"
8 "strings"
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/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 lastRegisteredTokenID 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, error) {
71 parser := newParser(strings.NewReader(set))
72 parsedSet, err := parser.parse()
73 if err != nil {
74 return nil, err
75 }
76
77 tc.Instructions = parsedSet.instructions
78 tc.accountsNames = parsedSet.accounts
79
80 tc.generateKeys(tc.accountsNames)
81
82 var blocks []BlockData
83 currBatchNum := 0
84 var currBlock BlockData
85 var currBatch BatchData
86 idx := 256
87 for _, inst := range parsedSet.instructions {
88 switch inst.typ {
89 case common.TxTypeCreateAccountDeposit, common.TxTypeCreateAccountDepositTransfer, txTypeCreateAccountDepositCoordinator:
90 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
91 return nil, err
92 }
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 if inst.typ == txTypeCreateAccountDepositCoordinator {
114 tx.Type = common.TxTypeCreateAccountDeposit // as txTypeCreateAccountDepositCoordinator is not valid oustide Transakcio package
115 currBatch.L1CoordinatorTxs = append(currBatch.L1CoordinatorTxs, tx)
116 } else {
117 currBatch.L1UserTxs = append(currBatch.L1UserTxs, tx)
118 }
119 case common.TxTypeDeposit, common.TxTypeDepositTransfer:
120 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
121 return nil, err
122 }
123 if tc.Users[inst.from].Accounts[inst.tokenID] == nil {
124 return nil, fmt.Errorf("Deposit at User %s for TokenID %d while account not created yet", inst.from, inst.tokenID)
125 }
126 tx := common.L1Tx{
127 // TxID
128 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
129 FromEthAddr: tc.Users[inst.from].Addr,
130 FromBJJ: tc.Users[inst.from].BJJ.Public(),
131 TokenID: inst.tokenID,
132 LoadAmount: big.NewInt(int64(inst.loadAmount)),
133 Type: inst.typ,
134 }
135 if tc.Users[inst.from].Accounts[inst.tokenID].Idx == common.Idx(0) {
136 // if account.Idx is not set yet, set it and increment idx
137 tc.Users[inst.from].Accounts[inst.tokenID].Idx = common.Idx(idx)
138
139 tc.l1CreatedAccounts[idxTokenIDToString(inst.from, inst.tokenID)] = tc.Users[inst.from].Accounts[inst.tokenID]
140 idx++
141 }
142 if inst.typ == common.TxTypeDepositTransfer {
143 tx.Amount = big.NewInt(int64(inst.amount))
144 // if ToIdx is not set yet, set it and increment idx
145 if tc.Users[inst.to].Accounts[inst.tokenID].Idx == common.Idx(0) {
146 tc.Users[inst.to].Accounts[inst.tokenID].Idx = common.Idx(idx)
147
148 tc.l1CreatedAccounts[idxTokenIDToString(inst.to, inst.tokenID)] = tc.Users[inst.to].Accounts[inst.tokenID]
149 tx.ToIdx = common.Idx(idx)
150 idx++
151 } else {
152 // if Idx account of To already exist, use it for ToIdx
153 tx.ToIdx = tc.Users[inst.to].Accounts[inst.tokenID].Idx
154 }
155 }
156 currBatch.L1UserTxs = append(currBatch.L1UserTxs, tx)
157 case common.TxTypeTransfer:
158 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
159 return nil, err
160 }
161 if tc.Users[inst.from].Accounts[inst.tokenID] == nil {
162 return nil, fmt.Errorf("Transfer from User %s for TokenID %d while account not created yet", inst.from, inst.tokenID)
163 }
164 // if account of receiver does not exist, create a new CoordinatorL1Tx creating the account
165 if _, ok := tc.l1CreatedAccounts[idxTokenIDToString(inst.to, inst.tokenID)]; !ok {
166 return nil, fmt.Errorf("Can not create Transfer for a non existing account. Batch %d, Instruction: %s", currBatchNum, inst)
167 }
168 tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
169 tx := common.L2Tx{
170 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
171 ToIdx: tc.Users[inst.to].Accounts[inst.tokenID].Idx,
172 Amount: big.NewInt(int64(inst.amount)),
173 Fee: common.FeeSelector(inst.fee),
174 Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
175 Type: common.TxTypeTransfer,
176 }
177 nTx, err := common.NewPoolL2Tx(tx.PoolL2Tx())
178 if err != nil {
179 return nil, err
180 }
181 tx = nTx.L2Tx()
182 tx.BatchNum = common.BatchNum(currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost
183
184 currBatch.L2Txs = append(currBatch.L2Txs, tx)
185 case common.TxTypeExit:
186 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
187 return nil, err
188 }
189 tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
190 tx := common.L2Tx{
191 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
192 ToIdx: common.Idx(1), // as is an Exit
193 Amount: big.NewInt(int64(inst.amount)),
194 Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
195 Type: common.TxTypeExit,
196 }
197 nTx, err := common.NewPoolL2Tx(tx.PoolL2Tx())
198 if err != nil {
199 return nil, err
200 }
201 tx = nTx.L2Tx()
202 currBatch.L2Txs = append(currBatch.L2Txs, tx)
203 case common.TxTypeForceExit:
204 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
205 return nil, err
206 }
207 tx := common.L1Tx{
208 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
209 ToIdx: common.Idx(1), // as is an Exit
210 TokenID: inst.tokenID,
211 Amount: big.NewInt(int64(inst.amount)),
212 Type: common.TxTypeExit,
213 }
214 currBatch.L1UserTxs = append(currBatch.L1UserTxs, tx)
215 case typeNewBatch:
216 currBlock.Batches = append(currBlock.Batches, currBatch)
217 currBatchNum++
218 currBatch = BatchData{}
219 case typeNewBlock:
220 currBlock.Batches = append(currBlock.Batches, currBatch)
221 currBatchNum++
222 currBatch = BatchData{}
223 blocks = append(blocks, currBlock)
224 currBlock = BlockData{}
225 case typeRegisterToken:
226 newToken := common.Token{
227 TokenID: inst.tokenID,
228 EthBlockNum: int64(len(blocks)),
229 }
230 if inst.tokenID != tc.lastRegisteredTokenID+1 {
231 return nil, fmt.Errorf("RegisterToken TokenID should be sequential, expected TokenID: %d, defined TokenID: %d", tc.lastRegisteredTokenID+1, inst.tokenID)
232 }
233 tc.lastRegisteredTokenID++
234 currBlock.RegisteredTokens = append(currBlock.RegisteredTokens, newToken)
235 default:
236 return nil, fmt.Errorf("Unexpected type: %s", inst.typ)
237 }
238 }
239 currBlock.Batches = append(currBlock.Batches, currBatch)
240 blocks = append(blocks, currBlock)
241
242 return blocks, nil
243}
244func (tc *TestContext) checkIfTokenIsRegistered(inst instruction) error {
245 if inst.tokenID > tc.lastRegisteredTokenID {
246 return fmt.Errorf("Can not process %s: TokenID %d not registered, last registered TokenID: %d", inst.typ, inst.tokenID, tc.lastRegisteredTokenID)
247 }
248 return nil
249}
250
251// GeneratePoolL2Txs returns an array of common.PoolL2Tx from a given set. It
252// uses the accounts (keys & nonces) of the TestContext.
253func (tc *TestContext) GeneratePoolL2Txs(set string) ([]common.PoolL2Tx, error) {
254 parser := newParser(strings.NewReader(set))
255 parsedSet, err := parser.parse()
256 if err != nil {
257 return nil, err
258 }
259
260 tc.Instructions = parsedSet.instructions
261 tc.accountsNames = parsedSet.accounts
262
263 tc.generateKeys(tc.accountsNames)
264
265 txs := []common.PoolL2Tx{}
266 for _, inst := range tc.Instructions {
267 switch inst.typ {
268 case common.TxTypeTransfer:
269 if tc.Users[inst.from].Accounts[inst.tokenID] == nil {
270 return nil, fmt.Errorf("Transfer from User %s for TokenID %d while account not created yet", inst.from, inst.tokenID)
271 }
272 if tc.Users[inst.to].Accounts[inst.tokenID] == nil {
273 return nil, fmt.Errorf("Transfer to User %s for TokenID %d while account not created yet", inst.to, inst.tokenID)
274 }
275 tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
276 // if account of receiver does not exist, don't use
277 // ToIdx, and use only ToEthAddr & ToBJJ
278 tx := common.PoolL2Tx{
279 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
280 ToIdx: tc.Users[inst.to].Accounts[inst.tokenID].Idx,
281 ToEthAddr: tc.Users[inst.to].Addr,
282 ToBJJ: tc.Users[inst.to].BJJ.Public(),
283 TokenID: inst.tokenID,
284 Amount: big.NewInt(int64(inst.amount)),
285 Fee: common.FeeSelector(inst.fee),
286 Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
287 State: common.PoolL2TxStatePending,
288 Timestamp: time.Now(),
289 RqToEthAddr: common.EmptyAddr,
290 RqToBJJ: nil,
291 Type: common.TxTypeTransfer,
292 }
293 nTx, err := common.NewPoolL2Tx(&tx)
294 if err != nil {
295 return nil, err
296 }
297 tx = *nTx
298 // perform signature and set it to tx.Signature
299 toSign, err := tx.HashToSign()
300 if err != nil {
301 return nil, err
302 }
303 sig := tc.Users[inst.to].BJJ.SignPoseidon(toSign)
304 tx.Signature = sig
305
306 txs = append(txs, tx)
307 case common.TxTypeExit:
308 tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
309 tx := common.PoolL2Tx{
310 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
311 ToIdx: common.Idx(1), // as is an Exit
312 TokenID: inst.tokenID,
313 Amount: big.NewInt(int64(inst.amount)),
314 Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
315 Type: common.TxTypeExit,
316 }
317 txs = append(txs, tx)
318 default:
319 return nil, fmt.Errorf("instruction type unrecognized: %s", inst.typ)
320 }
321 }
322
323 return txs, nil
324}
325
326// generateKeys generates BabyJubJub & Address keys for the given list of
327// account names in a deterministic way. This means, that for the same given
328// 'accNames' in a certain order, the keys will be always the same.
329func (tc *TestContext) generateKeys(accNames []string) {
330 for i := 1; i < len(accNames)+1; i++ {
331 if _, ok := tc.Users[accNames[i-1]]; ok {
332 // account already created
333 continue
334 }
335 // babyjubjub key
336 var sk babyjub.PrivateKey
337 copy(sk[:], []byte(strconv.Itoa(i))) // only for testing
338
339 // eth address
340 var key ecdsa.PrivateKey
341 key.D = big.NewInt(int64(i)) // only for testing
342 key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
343 key.Curve = ethCrypto.S256()
344 addr := ethCrypto.PubkeyToAddress(key.PublicKey)
345
346 u := User{
347 BJJ: &sk,
348 Addr: addr,
349 Accounts: make(map[common.TokenID]*Account),
350 }
351 tc.Users[accNames[i-1]] = &u
352 }
353}