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	"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}