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