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
19type TestContext struct {
20 t *testing.T
21 Instructions []Instruction
22 accountsNames []string
23 accounts map[string]*Account
24 TokenIDs []common.TokenID
25 l1CreatedAccounts map[string]*Account
26}
27
28func NewTestContext(t *testing.T) *TestContext {
29 return &TestContext{
30 t: t,
31 accounts: make(map[string]*Account),
32 l1CreatedAccounts: make(map[string]*Account),
33 }
34}
35
36// Account contains the data related to a testing account
37type Account struct {
38 BJJ *babyjub.PrivateKey
39 Addr ethCommon.Address
40 Idx common.Idx
41 Nonce common.Nonce
42}
43
44// func (tc *TestContext) GenerateBlocks() []BlockData {
45//
46// return nil
47// }
48
49// GeneratePoolL2Txs returns an array of common.PoolL2Tx from a given set. It
50// uses the accounts (keys & nonces) of the TestContext.
51func (tc *TestContext) GeneratePoolL2Txs(set string) []common.PoolL2Tx {
52 parser := NewParser(strings.NewReader(set))
53 parsedSet, err := parser.Parse()
54 require.Nil(tc.t, err)
55
56 tc.Instructions = parsedSet.Instructions
57 tc.accountsNames = parsedSet.Accounts
58 tc.TokenIDs = parsedSet.TokenIDs
59
60 tc.generateKeys(tc.accountsNames)
61
62 txs := []common.PoolL2Tx{}
63 for _, inst := range tc.Instructions {
64 switch inst.Type {
65 case common.TxTypeTransfer:
66 tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce++
67 // if account of receiver does not exist, don't use
68 // ToIdx, and use only ToEthAddr & ToBJJ
69 toIdx := new(common.Idx)
70 if _, ok := tc.l1CreatedAccounts[idxTokenIDToString(inst.To, inst.TokenID)]; !ok {
71 *toIdx = 0
72 } else {
73 *toIdx = tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Idx
74 }
75 // TODO once common.L{x}Txs parameter pointers is undone, update this lines related to pointers usage
76 toEthAddr := new(ethCommon.Address)
77 *toEthAddr = tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Addr
78 rqToEthAddr := new(ethCommon.Address)
79 *rqToEthAddr = tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].Addr
80 tx := common.PoolL2Tx{
81 FromIdx: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx,
82 ToIdx: toIdx,
83 ToEthAddr: toEthAddr,
84 ToBJJ: tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].BJJ.Public(),
85 TokenID: inst.TokenID,
86 Amount: big.NewInt(int64(inst.Amount)),
87 Fee: common.FeeSelector(inst.Fee),
88 Nonce: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce,
89 State: common.PoolL2TxStatePending,
90 Timestamp: time.Now(),
91 BatchNum: nil,
92 RqToEthAddr: rqToEthAddr,
93 RqToBJJ: tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].BJJ.Public(),
94 Type: common.TxTypeTransfer,
95 }
96 nTx, err := common.NewPoolL2Tx(&tx)
97 if err != nil {
98 panic(err)
99 }
100 tx = *nTx
101 // perform signature and set it to tx.Signature
102 toSign, err := tx.HashToSign()
103 if err != nil {
104 panic(err)
105 }
106 sig := tc.accounts[idxTokenIDToString(inst.To, inst.TokenID)].BJJ.SignPoseidon(toSign)
107 tx.Signature = sig
108
109 txs = append(txs, tx)
110 case common.TxTypeExit:
111 tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce++
112 // TODO once common.L{x}Txs parameter pointers is undone, update this lines related to pointers usage
113 toIdx := new(common.Idx)
114 *toIdx = common.Idx(1) // as is an Exit
115 tx := common.PoolL2Tx{
116 FromIdx: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx,
117 ToIdx: toIdx, // as is an Exit
118 TokenID: inst.TokenID,
119 Amount: big.NewInt(int64(inst.Amount)),
120 Nonce: tc.accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce,
121 Type: common.TxTypeExit,
122 }
123 txs = append(txs, tx)
124 default:
125 log.Warnf("instruction type unrecognized: %s", inst.Type)
126 continue
127 }
128 }
129
130 return txs
131}
132
133// generateKeys generates BabyJubJub & Address keys for the given list of
134// account names in a deterministic way. This means, that for the same given
135// 'accNames' in a certain order, the keys will be always the same.
136func (tc *TestContext) generateKeys(accNames []string) map[string]*Account {
137 acc := make(map[string]*Account)
138 for i := 1; i < len(accNames)+1; i++ {
139 if _, ok := tc.accounts[accNames[i-1]]; ok {
140 // account already created
141 continue
142 }
143 // babyjubjub key
144 var sk babyjub.PrivateKey
145 copy(sk[:], []byte(strconv.Itoa(i))) // only for testing
146
147 // eth address
148 var key ecdsa.PrivateKey
149 key.D = big.NewInt(int64(i)) // only for testing
150 key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
151 key.Curve = ethCrypto.S256()
152 addr := ethCrypto.PubkeyToAddress(key.PublicKey)
153
154 a := Account{
155 BJJ: &sk,
156 Addr: addr,
157 Nonce: 0,
158 }
159 tc.accounts[accNames[i-1]] = &a
160 }
161 return acc
162}
163
164/*
165// GenerateTestTxs generates L1Tx & PoolL2Tx in a deterministic way for the
166// given ParsedSet.
167func GenerateTestTxs(t *testing.T, parsedSet *ParsedSet) ([][]common.L1Tx, [][]common.L1Tx, [][]common.PoolL2Tx, []common.Token) {
168 accounts := generateKeys(t, parsedSet.Accounts)
169 l1CreatedAccounts := make(map[string]*Account)
170
171 var batchL1Txs []common.L1Tx
172 var batchCoordinatorL1Txs []common.L1Tx
173 var batchPoolL2Txs []common.PoolL2Tx
174 var l1Txs [][]common.L1Tx
175 var coordinatorL1Txs [][]common.L1Tx
176 var poolL2Txs [][]common.PoolL2Tx
177 idx := 256
178 for _, inst := range parsedSet.Instructions {
179 switch inst.Type {
180 case common.TxTypeCreateAccountDeposit:
181 tx := common.L1Tx{
182 // TxID
183 FromEthAddr: accounts[idxTokenIDToString(inst.From, inst.TokenID)].Addr,
184 FromBJJ: accounts[idxTokenIDToString(inst.From, inst.TokenID)].BJJ.Public(),
185 TokenID: inst.TokenID,
186 Amount: big.NewInt(0),
187 LoadAmount: big.NewInt(int64(inst.Amount)),
188 Type: common.TxTypeCreateAccountDeposit,
189 }
190 batchL1Txs = append(batchL1Txs, tx)
191 if accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx == common.Idx(0) { // if account.Idx is not set yet, set it and increment idx
192 accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx = common.Idx(idx)
193
194 l1CreatedAccounts[idxTokenIDToString(inst.From, inst.TokenID)] = accounts[idxTokenIDToString(inst.From, inst.TokenID)]
195 idx++
196 }
197 case common.TxTypeTransfer:
198 // if account of receiver does not exist, create a new CoordinatorL1Tx creating the account
199 if _, ok := l1CreatedAccounts[idxTokenIDToString(inst.To, inst.TokenID)]; !ok {
200 tx := common.L1Tx{
201 FromEthAddr: accounts[idxTokenIDToString(inst.To, inst.TokenID)].Addr,
202 FromBJJ: accounts[idxTokenIDToString(inst.To, inst.TokenID)].BJJ.Public(),
203 TokenID: inst.TokenID,
204 LoadAmount: big.NewInt(int64(inst.Amount)),
205 Type: common.TxTypeCreateAccountDeposit,
206 }
207 accounts[idxTokenIDToString(inst.To, inst.TokenID)].Idx = common.Idx(idx)
208 l1CreatedAccounts[idxTokenIDToString(inst.To, inst.TokenID)] = accounts[idxTokenIDToString(inst.To, inst.TokenID)]
209 batchCoordinatorL1Txs = append(batchCoordinatorL1Txs, tx)
210 idx++
211 }
212 tx := common.PoolL2Tx{
213 FromIdx: accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx,
214 ToIdx: accounts[idxTokenIDToString(inst.To, inst.TokenID)].Idx,
215 ToEthAddr: accounts[idxTokenIDToString(inst.To, inst.TokenID)].Addr,
216 ToBJJ: accounts[idxTokenIDToString(inst.To, inst.TokenID)].BJJ.Public(),
217 TokenID: inst.TokenID,
218 Amount: big.NewInt(int64(inst.Amount)),
219 Fee: common.FeeSelector(inst.Fee),
220 Nonce: accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce,
221 State: common.PoolL2TxStatePending,
222 RqToEthAddr: accounts[idxTokenIDToString(inst.To, inst.TokenID)].Addr,
223 RqToBJJ: accounts[idxTokenIDToString(inst.To, inst.TokenID)].BJJ.Public(),
224 Type: common.TxTypeTransfer,
225 }
226 nTx, err := common.NewPoolL2Tx(&tx)
227 if err != nil {
228 panic(err)
229 }
230 tx = *nTx
231 // perform signature and set it to tx.Signature
232 toSign, err := tx.HashToSign()
233 if err != nil {
234 panic(err)
235 }
236 sig := accounts[idxTokenIDToString(inst.To, inst.TokenID)].BJJ.SignPoseidon(toSign)
237 tx.Signature = sig
238
239 accounts[idxTokenIDToString(inst.From, inst.TokenID)].Nonce++
240 batchPoolL2Txs = append(batchPoolL2Txs, tx)
241
242 case common.TxTypeExit, common.TxTypeForceExit:
243 tx := common.L1Tx{
244 FromIdx: accounts[idxTokenIDToString(inst.From, inst.TokenID)].Idx,
245 ToIdx: common.Idx(1), // as is an Exit
246 TokenID: inst.TokenID,
247 Amount: big.NewInt(int64(inst.Amount)),
248 Type: common.TxTypeExit,
249 }
250 batchL1Txs = append(batchL1Txs, tx)
251 case TypeNewBatch:
252 l1Txs = append(l1Txs, batchL1Txs)
253 coordinatorL1Txs = append(coordinatorL1Txs, batchCoordinatorL1Txs)
254 poolL2Txs = append(poolL2Txs, batchPoolL2Txs)
255 batchL1Txs = []common.L1Tx{}
256 batchCoordinatorL1Txs = []common.L1Tx{}
257 batchPoolL2Txs = []common.PoolL2Tx{}
258 default:
259 continue
260 }
261 }
262 l1Txs = append(l1Txs, batchL1Txs)
263 coordinatorL1Txs = append(coordinatorL1Txs, batchCoordinatorL1Txs)
264 poolL2Txs = append(poolL2Txs, batchPoolL2Txs)
265 tokens := []common.Token{}
266 for i := 0; i < len(poolL2Txs); i++ {
267 for j := 0; j < len(poolL2Txs[i]); j++ {
268 id := poolL2Txs[i][j].TokenID
269 found := false
270 for k := 0; k < len(tokens); k++ {
271 if tokens[k].TokenID == id {
272 found = true
273 break
274 }
275 }
276 if !found {
277 tokens = append(tokens, common.Token{
278 TokenID: id,
279 EthBlockNum: 1,
280 EthAddr: ethCommon.BigToAddress(big.NewInt(int64(i*10000 + j))),
281 })
282 }
283 }
284 }
285 return l1Txs, coordinatorL1Txs, poolL2Txs, tokens
286}
287
288// GenerateTestTxsFromSet reurns the L1 & L2 transactions for a given Set of
289// Instructions code
290func GenerateTestTxsFromSet(t *testing.T, set string) ([][]common.L1Tx, [][]common.L1Tx, [][]common.PoolL2Tx, []common.Token) {
291 parser := NewParser(strings.NewReader(set))
292 parsedSet, err := parser.Parse()
293 require.Nil(t, err)
294
295 return GenerateTestTxs(t, parsedSet)
296}
297*/