arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
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}