From 827e917fa03110bf6b6b184eabc3c49363c667b0 Mon Sep 17 00:00:00 2001 From: Eduard S Date: Fri, 9 Oct 2020 12:54:16 +0200 Subject: [PATCH] Update missing parts, improve til, and more - Node - Updated configuration to initialize the interface to all the smart contracts - Common - Moved BlockData and BatchData types to common so that they can be shared among: historydb, til and synchronizer - Remove hash.go (it was never used) - Remove slot.go (it was never used) - Remove smartcontractparams.go (it was never used, and appropriate structs are defined in `eth/`) - Comment state / status method until requirements of this method are properly defined, and move it to Synchronizer - Synchronizer - Simplify `Sync` routine to only sync one block per call, and return useful information. - Use BlockData and BatchData from common - Check that events belong to the expected block hash - In L1Batch, query L1UserTxs from HistoryDB - Fill ERC20 token information - Test AddTokens with test.Client - HistryDB - Use BlockData and BatchData from common - Add `GetAllTokens` method - Uncomment and update GetL1UserTxs (with corresponding tests) - Til - Rename all instances of RegisterToken to AddToken (to follow the smart contract implementation naming) - Use BlockData and BatchData from common - Move testL1CoordinatorTxs and testL2Txs to a separate struct from BatchData in Context - Start Context with BatchNum = 1 (which the protocol defines to be the first batchNum) - In every Batch, set StateRoot and ExitRoot to a non-nil big.Int (zero). - In all L1Txs, if LoadAmount is not used, set it to 0; if Amount is not used, set it to 0; so that no *big.Int is nil. - In L1UserTx, don't set BatchNum, because when L1UserTxs are created and obtained by the synchronizer, the BatchNum is not known yet (it's a synchronizer job to set it) - In L1UserTxs, set `UserOrigin` and set `ToForgeL1TxsNum`. --- cli/node/cfg.example.toml | 18 +- common/batch.go | 6 +- common/bid.go | 2 +- common/block.go | 43 +++ common/hash.go | 4 - common/slot.go | 28 -- common/smartcontractparams.go | 21 -- common/syncstate.go | 16 - common/syncstatus.go | 12 - config/config.go | 12 + db/historydb/historydb.go | 125 +++----- db/historydb/historydb_test.go | 88 ++++++ go.mod | 1 + node/node.go | 55 +++- synchronizer/synchronizer.go | 484 +++++++++++++++--------------- synchronizer/synchronizer_test.go | 138 ++++++++- test/historydb.go | 8 +- test/til/README.md | 4 +- test/til/lang.go | 12 +- test/til/lang_test.go | 16 +- test/til/sets.go | 6 +- test/til/txs.go | 97 +++--- test/til/txs_test.go | 70 ++--- 23 files changed, 729 insertions(+), 537 deletions(-) delete mode 100644 common/hash.go delete mode 100644 common/slot.go delete mode 100644 common/smartcontractparams.go delete mode 100644 common/syncstate.go delete mode 100644 common/syncstatus.go diff --git a/cli/node/cfg.example.toml b/cli/node/cfg.example.toml index 6aab4f1..591f346 100644 --- a/cli/node/cfg.example.toml +++ b/cli/node/cfg.example.toml @@ -16,11 +16,17 @@ TTL = "24h" [Web3] URL = "XXX" -[TxSelector] -Path = "/tmp/iden3-test/hermez/txselector" - -[BatchBuilder] -Path = "/tmp/iden3-test/hermez/batchbuilder" - [Synchronizer] SyncLoopInterval = "1s" + +[SmartContracts] +Rollup = "0xEcc0a6dbC0bb4D51E4F84A315a9e5B0438cAD4f0" +Auction = "0x500D1d6A4c7D8Ae28240b47c8FCde034D827fD5e" +TokenHEZ = "0xf784709d2317D872237C4bC22f867d1BAe2913AB" + +[EthClient] +CallGasLimit = 300000 +DeployGasLimit = 1000000 +GasPriceDiv = 100 +ReceiptTimeout = "60s" +IntervalReceiptLoop = "200ms" diff --git a/common/batch.go b/common/batch.go index 035b9fb..d132442 100644 --- a/common/batch.go +++ b/common/batch.go @@ -16,11 +16,11 @@ type Batch struct { EthBlockNum int64 `meddler:"eth_block_num"` // Ethereum block in which the batch is forged ForgerAddr ethCommon.Address `meddler:"forger_addr"` // TODO: Should this be retrieved via slot reference? CollectedFees map[TokenID]*big.Int `meddler:"fees_collected,json"` - StateRoot Hash `meddler:"state_root"` + StateRoot *big.Int `meddler:"state_root,bigint"` NumAccounts int `meddler:"num_accounts"` - ExitRoot Hash `meddler:"exit_root"` + ExitRoot *big.Int `meddler:"exit_root,bigint"` ForgeL1TxsNum *int64 `meddler:"forge_l1_txs_num"` // optional, Only when the batch forges L1 txs. Identifier that corresponds to the group of L1 txs forged in the current batch. - SlotNum SlotNum `meddler:"slot_num"` // Slot in which the batch is forged + SlotNum int64 `meddler:"slot_num"` // Slot in which the batch is forged TotalFeesUSD *float64 `meddler:"total_fees_usd"` } diff --git a/common/bid.go b/common/bid.go index a661c37..842d39d 100644 --- a/common/bid.go +++ b/common/bid.go @@ -8,7 +8,7 @@ import ( // Bid is a struct that represents one bid in the PoH type Bid struct { - SlotNum SlotNum `meddler:"slot_num"` + SlotNum int64 `meddler:"slot_num"` BidValue *big.Int `meddler:"bid_value,bigint"` EthBlockNum int64 `meddler:"eth_block_num"` Bidder ethCommon.Address `meddler:"bidder_addr"` // Coordinator reference diff --git a/common/block.go b/common/block.go index 13db01e..fa3f4e5 100644 --- a/common/block.go +++ b/common/block.go @@ -13,3 +13,46 @@ type Block struct { Hash ethCommon.Hash `meddler:"hash"` ParentHash ethCommon.Hash `meddler:"-"` } + +// BlockData contains the information of a Block +type BlockData struct { + Block Block + // Rollup + // L1UserTxs that were submitted in the block + L1UserTxs []L1Tx + Batches []BatchData + AddedTokens []Token + RollupVars *RollupVars + // Auction + Bids []Bid + Coordinators []Coordinator + AuctionVars *AuctionVars + WithdrawDelayerVars *WithdrawDelayerVars + // TODO: enable when common.WithdrawalDelayerVars is Merged from Synchronizer PR + // WithdrawalDelayerVars *common.WithdrawalDelayerVars +} + +// BatchData contains the information of a Batch +type BatchData struct { + // L1UserTxs that were forged in the batch + L1Batch bool // TODO: Remove once Batch.ForgeL1TxsNum is a pointer + // L1UserTxs []common.L1Tx + L1CoordinatorTxs []L1Tx + L2Txs []L2Tx + CreatedAccounts []Account + ExitTree []ExitInfo + Batch Batch +} + +// NewBatchData creates an empty BatchData with the slices initialized. +func NewBatchData() *BatchData { + return &BatchData{ + L1Batch: false, + // L1UserTxs: make([]common.L1Tx, 0), + L1CoordinatorTxs: make([]L1Tx, 0), + L2Txs: make([]L2Tx, 0), + CreatedAccounts: make([]Account, 0), + ExitTree: make([]ExitInfo, 0), + Batch: Batch{}, + } +} diff --git a/common/hash.go b/common/hash.go deleted file mode 100644 index 3854ff7..0000000 --- a/common/hash.go +++ /dev/null @@ -1,4 +0,0 @@ -package common - -// Hash is the used hash for Hermez network -type Hash []byte diff --git a/common/slot.go b/common/slot.go deleted file mode 100644 index b13695c..0000000 --- a/common/slot.go +++ /dev/null @@ -1,28 +0,0 @@ -package common - -import ( - "math/big" -) - -// Slot represents a slot of the Hermez network -// WARNING: this is strongly based on the previous implementation, once the new spec is done, this may change a lot. -type Slot struct { - SlotNum SlotNum - StartingBlock uint64 // Ethereum block in which the slot starts - Forger Coordinator // Current Operaror winner information -} - -// SlotMinPrice is the policy of minimum prices for strt bidding in the slots -type SlotMinPrice struct { - EthBlockNum uint64 // Etherum block in which the min price was updated - MinPrices [6]big.Int -} - -// GetMinPrice returns the minimum bid to enter the auction for a specific slot -func (smp *SlotMinPrice) GetMinPrice(slotNum SlotNum) *big.Int { - // TODO - return nil -} - -// SlotNum identifies a slot -type SlotNum uint32 diff --git a/common/smartcontractparams.go b/common/smartcontractparams.go deleted file mode 100644 index 51ed380..0000000 --- a/common/smartcontractparams.go +++ /dev/null @@ -1,21 +0,0 @@ -package common - -import ( - "math/big" - - ethCommon "github.com/ethereum/go-ethereum/common" -) - -// SmartContractParameters describes the constant values of the parameters of the Hermez smart contracts -// WARNING: not stable at all -type SmartContractParameters struct { - SlotDuration uint64 // number of ethereum blocks in a slot - Slot0BlockNum uint64 // ethereum block number of the first slot (slot 0) - MaxL1UserTxs uint64 // maximum number of L1UserTxs that can be queued for a single batch - FreeCoordinatorWait uint64 // if the winning coordinator doesn't forge during this number of blocks, anyone can forge - ContractAddr ethCommon.Address // Ethereum address of the rollup smart contract - NLevels uint16 // Heigth of the SMT. This will determine the maximum number of accounts that can coexist in the Hermez network by 2^nLevels - MaxTxs uint16 // Max amount of txs that can be added in a batch, either L1 or L2 - FeeL1Tx *big.Int // amount of eth (in wei) that has to be paid to do a L1 tx - FeeDeposit *big.Int // amount of eth (in wei) that has to be paid to do a deposit -} diff --git a/common/syncstate.go b/common/syncstate.go deleted file mode 100644 index 7ac271d..0000000 --- a/common/syncstate.go +++ /dev/null @@ -1,16 +0,0 @@ -package common - -import ( - "time" -) - -// SyncronizerState describes the synchronization progress of the smart contracts -type SyncronizerState struct { - LastUpdate time.Time // last time this information was updated - CurrentBatchNum BatchNum // Last batch that was forged on the blockchain - CurrentBlockNum uint64 // Last block that was mined on Ethereum - CurrentToForgeL1TxsNum uint32 - LastSyncedBatchNum BatchNum // last batch synchronized by the coordinator - LastSyncedBlockNum uint64 // last Ethereum block synchronized by the coordinator - LastSyncedToForgeL1TxsNum uint32 -} diff --git a/common/syncstatus.go b/common/syncstatus.go deleted file mode 100644 index de65b94..0000000 --- a/common/syncstatus.go +++ /dev/null @@ -1,12 +0,0 @@ -package common - -import ethCommon "github.com/ethereum/go-ethereum/common" - -// SyncStatus is returned by the Status method of the Synchronizer -type SyncStatus struct { - CurrentBlock int64 - CurrentBatch BatchNum - CurrentForgerAddr ethCommon.Address - NextForgerAddr ethCommon.Address - Synchronized bool -} diff --git a/config/config.go b/config/config.go index 821a9a7..f9846fa 100644 --- a/config/config.go +++ b/config/config.go @@ -67,6 +67,18 @@ type Node struct { Synchronizer struct { SyncLoopInterval Duration `validate:"required"` } `validate:"required"` + SmartContracts struct { + Rollup ethCommon.Address `validate:"required"` + Auction ethCommon.Address `validate:"required"` + TokenHEZ ethCommon.Address `validate:"required"` + } `validate:"required"` + EthClient struct { + CallGasLimit uint64 `validate:"required"` + DeployGasLimit uint64 `validate:"required"` + GasPriceDiv uint64 `validate:"required"` + ReceiptTimeout Duration `validate:"required"` + IntervalReceiptLoop Duration `validate:"required"` + } `validate:"required"` } // Load loads a generic config. diff --git a/db/historydb/historydb.go b/db/historydb/historydb.go index 8e5608c..7421d02 100644 --- a/db/historydb/historydb.go +++ b/db/historydb/historydb.go @@ -33,49 +33,6 @@ type HistoryDB struct { db *sqlx.DB } -// BlockData contains the information of a Block -type BlockData struct { - Block *common.Block - // Rollup - // L1UserTxs that were submitted in the block - L1UserTxs []common.L1Tx - Batches []BatchData - RegisteredTokens []common.Token - RollupVars *common.RollupVars - // Auction - Bids []common.Bid - Coordinators []common.Coordinator - AuctionVars *common.AuctionVars - WithdrawDelayerVars *common.WithdrawDelayerVars - // TODO: enable when common.WithdrawalDelayerVars is Merged from Synchronizer PR - // WithdrawalDelayerVars *common.WithdrawalDelayerVars -} - -// BatchData contains the information of a Batch -type BatchData struct { - // L1UserTxs that were forged in the batch - L1Batch bool // TODO: Remove once Batch.ForgeL1TxsNum is a pointer - L1UserTxs []common.L1Tx - L1CoordinatorTxs []common.L1Tx - L2Txs []common.L2Tx - CreatedAccounts []common.Account - ExitTree []common.ExitInfo - Batch *common.Batch -} - -// NewBatchData creates an empty BatchData with the slices initialized. -func NewBatchData() *BatchData { - return &BatchData{ - L1Batch: false, - L1UserTxs: make([]common.L1Tx, 0), - L1CoordinatorTxs: make([]common.L1Tx, 0), - L2Txs: make([]common.L2Tx, 0), - CreatedAccounts: make([]common.Account, 0), - ExitTree: make([]common.ExitInfo, 0), - Batch: &common.Batch{}, - } -} - // NewHistoryDB initialize the DB func NewHistoryDB(db *sqlx.DB) *HistoryDB { return &HistoryDB{db: db} @@ -149,19 +106,21 @@ func (hdb *HistoryDB) addBatch(d meddler.DB, batch *common.Batch) error { USD *float64 `meddler:"usd"` Decimals int `meddler:"decimals"` } - query, args, err := sqlx.In( - "SELECT token_id, usd, decimals FROM token WHERE token_id IN (?)", - tokenIDs, - ) - if err != nil { - return err - } - query = hdb.db.Rebind(query) var tokenPrices []*tokenPrice - if err := meddler.QueryAll( - hdb.db, &tokenPrices, query, args..., - ); err != nil { - return err + if len(tokenIDs) > 0 { + query, args, err := sqlx.In( + "SELECT token_id, usd, decimals FROM token WHERE token_id IN (?)", + tokenIDs, + ) + if err != nil { + return err + } + query = hdb.db.Rebind(query) + if err := meddler.QueryAll( + hdb.db, &tokenPrices, query, args..., + ); err != nil { + return err + } } // Calculate total collected var total float64 @@ -209,8 +168,8 @@ func (hdb *HistoryDB) GetLastBatchNum() (common.BatchNum, error) { return batchNum, row.Scan(&batchNum) } -// GetLastL1TxsNum returns the greatest ForgeL1TxsNum in the DB. If there's no -// batch in the DB (nil, nil) is returned. +// GetLastL1TxsNum returns the greatest ForgeL1TxsNum in the DB from forged +// batches. If there's no batch in the DB (nil, nil) is returned. func (hdb *HistoryDB) GetLastL1TxsNum() (*int64, error) { row := hdb.db.QueryRow("SELECT MAX(forge_l1_txs_num) FROM batch;") lastL1TxsNum := new(int64) @@ -326,6 +285,16 @@ func (hdb *HistoryDB) GetToken(tokenID common.TokenID) (*TokenRead, error) { return token, err } +// GetAllTokens returns all tokens from the DB +func (hdb *HistoryDB) GetAllTokens() ([]TokenRead, error) { + var tokens []*TokenRead + err := meddler.QueryAll( + hdb.db, &tokens, + "SELECT * FROM token ORDER BY token_id;", + ) + return db.SlicePtrsToSlice(tokens).([]TokenRead), err +} + // GetTokens returns a list of tokens from the DB func (hdb *HistoryDB) GetTokens(ids []common.TokenID, symbols []string, name string, fromItem, limit *uint, order string) ([]TokenRead, *db.Pagination, error) { var query string @@ -822,17 +791,19 @@ func (hdb *HistoryDB) GetExits( // ) // } -// // GetL1UserTxs gets L1 User Txs to be forged in a batch that will create an account -// // TODO: This is currently not used. Figure out if it should be used somewhere or removed. -// func (hdb *HistoryDB) GetL1UserTxs(toForgeL1TxsNum int64) ([]*common.Tx, error) { -// var txs []*common.Tx -// err := meddler.QueryAll( -// hdb.db, &txs, -// "SELECT * FROM tx WHERE to_forge_l1_txs_num = $1 AND is_l1 = TRUE AND user_origin = TRUE;", -// toForgeL1TxsNum, -// ) -// return txs, err -// } +// GetL1UserTxs gets L1 User Txs to be forged in the L1Batch with toForgeL1TxsNum. +func (hdb *HistoryDB) GetL1UserTxs(toForgeL1TxsNum int64) ([]common.L1Tx, error) { + var txs []*common.L1Tx + err := meddler.QueryAll( + hdb.db, &txs, + `SELECT tx.id, tx.to_forge_l1_txs_num, tx.position, tx.user_origin, + tx.from_idx, tx.from_eth_addr, tx.from_bjj, tx.to_idx, tx.token_id, tx.amount, + tx.load_amount, tx.eth_block_num, tx.type, tx.batch_num + FROM tx WHERE to_forge_l1_txs_num = $1 AND is_l1 = TRUE AND user_origin = TRUE;`, + toForgeL1TxsNum, + ) + return db.SlicePtrsToSlice(txs).([]common.L1Tx), err +} // TODO: Think about chaning all the queries that return a last value, to queries that return the next valid value. @@ -843,8 +814,11 @@ func (hdb *HistoryDB) GetLastTxsPosition(toForgeL1TxsNum int64) (int, error) { return lastL1TxsPosition, row.Scan(&lastL1TxsPosition) } -// AddBlockSCData stores all the information of a block retrieved by the Synchronizer -func (hdb *HistoryDB) AddBlockSCData(blockData *BlockData) (err error) { +// AddBlockSCData stores all the information of a block retrieved by the +// Synchronizer. Blocks should be inserted in order, leaving no gaps because +// the pagination system of the API/DB depends on this. Within blocks, all +// items should also be in the correct order (Accounts, Tokens, Txs, etc.) +func (hdb *HistoryDB) AddBlockSCData(blockData *common.BlockData) (err error) { txn, err := hdb.db.Begin() if err != nil { return err @@ -859,7 +833,7 @@ func (hdb *HistoryDB) AddBlockSCData(blockData *BlockData) (err error) { }() // Add block - err = hdb.addBlock(txn, blockData.Block) + err = hdb.addBlock(txn, &blockData.Block) if err != nil { return err } @@ -881,8 +855,8 @@ func (hdb *HistoryDB) AddBlockSCData(blockData *BlockData) (err error) { } // Add Tokens - if len(blockData.RegisteredTokens) > 0 { - err = hdb.addTokens(txn, blockData.RegisteredTokens) + if len(blockData.AddedTokens) > 0 { + err = hdb.addTokens(txn, blockData.AddedTokens) if err != nil { return err } @@ -897,10 +871,11 @@ func (hdb *HistoryDB) AddBlockSCData(blockData *BlockData) (err error) { } // Add Batches - for _, batch := range blockData.Batches { + for i := range blockData.Batches { + batch := &blockData.Batches[i] // Add Batch: this will trigger an update on the DB // that will set the batch num of forged L1 txs in this batch - err = hdb.addBatch(txn, batch.Batch) + err = hdb.addBatch(txn, &batch.Batch) if err != nil { return err } diff --git a/db/historydb/historydb_test.go b/db/historydb/historydb_test.go index 027c8ad..3434c3f 100644 --- a/db/historydb/historydb_test.go +++ b/db/historydb/historydb_test.go @@ -6,11 +6,14 @@ import ( "os" "testing" + ethCommon "github.com/ethereum/go-ethereum/common" "github.com/hermeznetwork/hermez-node/common" dbUtils "github.com/hermeznetwork/hermez-node/db" "github.com/hermeznetwork/hermez-node/log" "github.com/hermeznetwork/hermez-node/test" + "github.com/hermeznetwork/hermez-node/test/til" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var historyDB *HistoryDB @@ -378,6 +381,91 @@ func TestExitTree(t *testing.T) { assert.NoError(t, err) } +func TestGetL1UserTxs(t *testing.T) { + require.NoError(t, cleanHistoryDB()) + + set := ` + Type: Blockchain + AddToken(1) + AddToken(2) + AddToken(3) + + CreateAccountDeposit(1) A: 10 + CreateAccountDeposit(2) A: 20 + CreateAccountDeposit(1) B: 5 + CreateAccountDeposit(1) C: 5 + CreateAccountDepositTransfer(1) D-A: 15, 10 (3) + + > batchL1 // freeze open l1UserTxs queue + > batchL1 // forge current l1UserTxs queue + > block + ` + tc := til.NewContext(128) + blocks, err := tc.GenerateBlocks(set) + require.Nil(t, err) + // Sanity check + require.Equal(t, 1, len(blocks)) + require.Equal(t, 5, len(blocks[0].L1UserTxs)) + require.Equal(t, 2, len(blocks[0].Batches)) + // fmt.Printf("DBG Blocks: %+v\n", blocks) + + // TODO: Move this logic to `func (tc *TestContext) GenerateBlocks(set string) ([]common.BlockData, error)` + toForgeL1TxsNum := int64(1) + for i := range blocks { + block := &blocks[i] + block.Block.EthBlockNum = int64(i) + for j := range block.AddedTokens { + token := &block.AddedTokens[j] + token.EthAddr = ethCommon.BigToAddress(big.NewInt(int64(i*len(blocks) + j))) + } + for j := range block.L1UserTxs { + l1Tx := &block.L1UserTxs[j] + l1Tx.UserOrigin = true + l1Tx.Position = j + l1Tx.ToForgeL1TxsNum = &toForgeL1TxsNum + } + } + + for i := range blocks { + // fmt.Printf("DBG %+v\n", blocks[i]) + // fmt.Printf("DBG Batches %+v\n", blocks[i].Batches) + // for _, l1Tx := range blocks[i].L1UserTxs { + // fmt.Printf("DBG l1UserTx %+v\n", l1Tx) + // } + err = historyDB.AddBlockSCData(&blocks[i]) + require.Nil(t, err) + } + + // // TODO: Use til to generate a set with some L1UserTxs + // l1Txs := []common.L1Tx{} + // l1Tx, err := common.NewL1Tx(&common.L1Tx{ + // ToForgeL1TxsNum: &toForgeL1TxsNum, + // Position: 0, + // UserOrigin: true, + // FromIdx: 0, + // FromEthAddr: ethCommon.Address{}, // ethCommon.HexToAddress("0xff"), + // FromBJJ: nil, + // ToIdx: 0, + // TokenID: 1, + // Amount: big.NewInt(0), + // LoadAmount: big.NewInt(0), + // }) + // require.Nil(t, err) + // l1Txs = append(l1Txs, *l1Tx) + + // require.Nil(t, historyDB.AddL1Txs(l1Txs)) + + l1UserTxs, err := historyDB.GetL1UserTxs(toForgeL1TxsNum) + require.Nil(t, err) + assert.Equal(t, 5, len(l1UserTxs)) + assert.Equal(t, blocks[0].L1UserTxs, l1UserTxs) + + // No l1UserTxs for this toForgeL1TxsNum + l1UserTxs, err = historyDB.GetL1UserTxs(2) + require.Nil(t, err) + assert.Equal(t, 0, len(l1UserTxs)) +} + // setTestBlocks WARNING: this will delete the blocks and recreate them func setTestBlocks(from, to int64) []common.Block { if err := cleanHistoryDB(); err != nil { diff --git a/go.mod b/go.mod index e7a81d3..e111650 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/gobuffalo/packr/v2 v2.8.0 github.com/iden3/go-iden3-crypto v0.0.6-0.20201016142444-94e92e88fb4e github.com/iden3/go-merkletree v0.0.0-20200902123354-eeb949f8c334 + github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jmoiron/sqlx v1.2.0 github.com/lib/pq v1.8.0 github.com/mitchellh/copystructure v1.0.0 diff --git a/node/node.go b/node/node.go index 2e1f0fd..e675528 100644 --- a/node/node.go +++ b/node/node.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/hermeznetwork/hermez-node/batchbuilder" + "github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/config" "github.com/hermeznetwork/hermez-node/coordinator" dbUtils "github.com/hermeznetwork/hermez-node/db" @@ -48,13 +49,14 @@ type Node struct { // Synchronizer sync *synchronizer.Synchronizer - stopSync chan bool stoppedSync chan bool // General cfg *config.Node mode Mode sqlConn *sqlx.DB + ctx context.Context + cancel context.CancelFunc } // NewNode creates a Node @@ -82,7 +84,22 @@ func NewNode(mode Mode, cfg *config.Node, coordCfg *config.Coordinator) (*Node, if err != nil { return nil, err } - client, err := eth.NewClient(ethClient, nil, nil, nil) + client, err := eth.NewClient(ethClient, nil, nil, ð.ClientConfig{ + Ethereum: eth.EthereumConfig{ + CallGasLimit: cfg.EthClient.CallGasLimit, + DeployGasLimit: cfg.EthClient.DeployGasLimit, + GasPriceDiv: cfg.EthClient.GasPriceDiv, + ReceiptTimeout: cfg.EthClient.ReceiptTimeout.Duration, + IntervalReceiptLoop: cfg.EthClient.IntervalReceiptLoop.Duration, + }, + Rollup: eth.RollupConfig{ + Address: cfg.SmartContracts.Rollup, + }, + Auction: eth.AuctionConfig{ + Address: cfg.SmartContracts.Auction, + TokenHEZAddress: cfg.SmartContracts.TokenHEZ, + }, + }) if err != nil { return nil, err } @@ -129,6 +146,7 @@ func NewNode(mode Mode, cfg *config.Node, coordCfg *config.Coordinator) (*Node, client, ) } + ctx, cancel := context.WithCancel(context.Background()) return &Node{ coord: coord, coordCfg: coordCfg, @@ -136,6 +154,8 @@ func NewNode(mode Mode, cfg *config.Node, coordCfg *config.Coordinator) (*Node, cfg: cfg, mode: mode, sqlConn: db, + ctx: ctx, + cancel: cancel, }, nil } @@ -220,28 +240,40 @@ func (n *Node) StopCoordinator() { // StartSynchronizer starts the synchronizer func (n *Node) StartSynchronizer() { log.Info("Starting Synchronizer...") - n.stopSync = make(chan bool) n.stoppedSync = make(chan bool) go func() { defer func() { n.stoppedSync <- true }() + var lastBlock *common.Block + d := time.Duration(0) for { select { - case <-n.stopSync: + case <-n.ctx.Done(): log.Info("Coordinator stopped") return - case <-time.After(n.cfg.Synchronizer.SyncLoopInterval.Duration): - if err := n.sync.Sync(context.TODO()); err != nil { + case <-time.After(d): + if blockData, discarded, err := n.sync.Sync2(n.ctx, lastBlock); err != nil { log.Errorw("Synchronizer.Sync", "error", err) + lastBlock = nil + d = n.cfg.Synchronizer.SyncLoopInterval.Duration + } else if discarded != nil { + log.Infow("Synchronizer.Sync reorg", "discarded", *discarded) + lastBlock = nil + d = time.Duration(0) + } else if blockData != nil { + lastBlock = &blockData.Block + d = time.Duration(0) + } else { + d = n.cfg.Synchronizer.SyncLoopInterval.Duration } } } }() + // TODO: Run price updater. This is required by the API and the TxSelector } -// StopSynchronizer stops the synchronizer -func (n *Node) StopSynchronizer() { - log.Info("Stopping Synchronizer...") - n.stopSync <- true +// WaitStopSynchronizer waits for the synchronizer to stop +func (n *Node) WaitStopSynchronizer() { + log.Info("Waiting for Synchronizer to stop...") <-n.stoppedSync } @@ -257,8 +289,9 @@ func (n *Node) Start() { // Stop the node func (n *Node) Stop() { log.Infow("Stopping node...") + n.cancel() if n.mode == ModeCoordinator { n.StopCoordinator() } - n.StopSynchronizer() + n.WaitStopSynchronizer() } diff --git a/synchronizer/synchronizer.go b/synchronizer/synchronizer.go index de671e0..ba41677 100644 --- a/synchronizer/synchronizer.go +++ b/synchronizer/synchronizer.go @@ -3,9 +3,8 @@ package synchronizer import ( "context" "database/sql" - "errors" - "sync" + "github.com/ethereum/go-ethereum" "github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/db/historydb" "github.com/hermeznetwork/hermez-node/db/statedb" @@ -14,26 +13,46 @@ import ( ) var ( - // ErrNotAbleToSync is used when there is not possible to find a valid block to sync - ErrNotAbleToSync = errors.New("it has not been possible to synchronize any block") +// ErrNotAbleToSync is used when there is not possible to find a valid block to sync +// ErrNotAbleToSync = errors.New("it has not been possible to synchronize any block") ) +// // SyncronizerState describes the synchronization progress of the smart contracts +// type SyncronizerState struct { +// LastUpdate time.Time // last time this information was updated +// CurrentBatchNum BatchNum // Last batch that was forged on the blockchain +// CurrentBlockNum uint64 // Last block that was mined on Ethereum +// CurrentToForgeL1TxsNum uint32 +// LastSyncedBatchNum BatchNum // last batch synchronized by the coordinator +// LastSyncedBlockNum uint64 // last Ethereum block synchronized by the coordinator +// LastSyncedToForgeL1TxsNum uint32 +// } + +// // SyncStatus is returned by the Status method of the Synchronizer +// type SyncStatus struct { +// CurrentBlock int64 +// CurrentBatch BatchNum +// CurrentForgerAddr ethCommon.Address +// NextForgerAddr ethCommon.Address +// Synchronized bool +// } + // rollupData contains information returned by the Rollup SC type rollupData struct { l1UserTxs []common.L1Tx - batches []historydb.BatchData + batches []common.BatchData // withdrawals []*common.ExitInfo - registeredTokens []common.Token - vars *common.RollupVars + addTokens []common.Token + vars *common.RollupVars } // NewRollupData creates an empty rollupData with the slices initialized. func newRollupData() rollupData { return rollupData{ l1UserTxs: make([]common.L1Tx, 0), - batches: make([]historydb.BatchData, 0), + batches: make([]common.BatchData, 0), // withdrawals: make([]*common.ExitInfo, 0), - registeredTokens: make([]common.Token, 0), + addTokens: make([]common.Token, 0), } } @@ -56,58 +75,21 @@ type wdelayerData struct { vars *common.WithdrawDelayerVars } -// BatchData contains information about Batches from the contracts -// type BatchData struct { -// l1UserTxs []*common.L1Tx -// l1CoordinatorTxs []*common.L1Tx -// l2Txs []*common.L2Tx -// createdAccounts []*common.Account -// exitTree []*common.ExitInfo -// batch *common.Batch -// } - -// NewBatchData creates an empty BatchData with the slices initialized. -// func NewBatchData() *BatchData { -// return &BatchData{ -// l1UserTxs: make([]*common.L1Tx, 0), -// l1CoordinatorTxs: make([]*common.L1Tx, 0), -// l2Txs: make([]*common.L2Tx, 0), -// createdAccounts: make([]*common.Account, 0), -// exitTree: make([]*common.ExitInfo, 0), -// } -// } - -// BlockData contains information about Blocks from the contracts -// type blockData struct { -// Block *common.Block -// // Rollup -// L1Txs []*common.L1Tx // TODO: Answer: User? Coordinator? Both? -// Batches []*BatchData // TODO: Also contains L1Txs! -// // withdrawals []*common.ExitInfo // TODO -// RegisteredTokens []common.Token -// RollupVars *common.RollupVars -// // Auction -// Bids []*common.Bid -// Coordinators []*common.Coordinator -// AuctionVars *common.AuctionVars -// // WithdrawalDelayer -// WithdrawalDelayerVars *common.WithdrawalDelayerVars -// } - // Synchronizer implements the Synchronizer type type Synchronizer struct { ethClient eth.ClientInterface auctionConstants eth.AuctionConstants historyDB *historydb.HistoryDB stateDB *statedb.StateDB - firstSavedBlock *common.Block - mux sync.Mutex + // firstSavedBlock *common.Block + // mux sync.Mutex } // NewSynchronizer creates a new Synchronizer func NewSynchronizer(ethClient eth.ClientInterface, historyDB *historydb.HistoryDB, stateDB *statedb.StateDB) (*Synchronizer, error) { auctionConstants, err := ethClient.AuctionConstants() if err != nil { + log.Errorw("NewSynchronizer", "err", err) return nil, err } return &Synchronizer{ @@ -118,128 +100,101 @@ func NewSynchronizer(ethClient eth.ClientInterface, historyDB *historydb.History }, nil } +// Sync2 attems to synchronize an ethereum block starting from lastSavedBlock. +// If lastSavedBlock is nil, the lastSavedBlock value is obtained from de DB. +// If a block is synched, it will be returned and also stored in the DB. If a +// reorg is detected, the number of discarded blocks will be returned and no +// synchronization will be made. // TODO: Be smart about locking: only lock during the read/write operations - -// Sync updates History and State DB with information from the blockchain -// TODO: Return true if a new block was processed -// TODO: Add argument: maximum number of blocks to process -// TODO: Check reorgs in the middle of syncing a block. Probably make -// rollupSync, auctionSync and withdrawalSync return the block hash. -func (s *Synchronizer) Sync(ctx context.Context) error { - // Avoid new sync while performing one - s.mux.Lock() - defer s.mux.Unlock() - +func (s *Synchronizer) Sync2(ctx context.Context, lastSavedBlock *common.Block) (*common.BlockData, *int64, error) { var nextBlockNum int64 // next block number to sync + if lastSavedBlock == nil { + var err error + // Get lastSavedBlock from History DB + lastSavedBlock, err = s.historyDB.GetLastBlock() + if err != nil && err != sql.ErrNoRows { + return nil, nil, err + } + // If we don't have any stored block, we must do a full sync starting from the rollup genesis block + if err == sql.ErrNoRows { + nextBlockNum = s.auctionConstants.GenesisBlockNum + } + } + if lastSavedBlock != nil { + nextBlockNum = lastSavedBlock.EthBlockNum + 1 + } - // Get lastSavedBlock from History DB - lastSavedBlock, err := s.historyDB.GetLastBlock() - if err != nil && err != sql.ErrNoRows { - return err + ethBlock, err := s.ethClient.EthBlockByNumber(ctx, nextBlockNum) + if err == ethereum.NotFound { + return nil, nil, nil + } else if err != nil { + return nil, nil, err } - // If we don't have any stored block, we must do a full sync starting from the rollup genesis block - if err == sql.ErrNoRows { - nextBlockNum = s.auctionConstants.GenesisBlockNum - } else { - // Get the latest block we have in History DB from blockchain to detect a reorg - ethBlock, err := s.ethClient.EthBlockByNumber(ctx, lastSavedBlock.EthBlockNum) - if err != nil { - return err - } - if ethBlock.Hash != lastSavedBlock.Hash { - // Reorg detected - log.Debugf("Reorg Detected...") - _, err := s.reorg(lastSavedBlock) - if err != nil { - return err - } + log.Debugw("Syncing...", "block", nextBlockNum) - lastSavedBlock, err = s.historyDB.GetLastBlock() + // Check that the obtianed ethBlock.ParentHash == prevEthBlock.Hash; if not, reorg! + if lastSavedBlock != nil { + if lastSavedBlock.Hash != ethBlock.ParentHash { + // Reorg detected + log.Debugw("Reorg Detected", + "blockNum", ethBlock.EthBlockNum, + "block.parent", ethBlock.ParentHash, "parent.hash", lastSavedBlock.Hash) + lastDBBlockNum, err := s.reorg(lastSavedBlock) if err != nil { - return err + return nil, nil, err } + discarded := lastSavedBlock.EthBlockNum - lastDBBlockNum + return nil, &discarded, nil } - nextBlockNum = lastSavedBlock.EthBlockNum + 1 } - log.Debugf("Syncing...") - - // Get latest blockNum in blockchain - latestBlockNum, err := s.ethClient.EthCurrentBlock() + // Get data from the rollup contract + rollupData, err := s.rollupSync(ethBlock) if err != nil { - return err + return nil, nil, err } - log.Debugf("Blocks to sync: %v (firstBlockToSync: %v, latestBlock: %v)", latestBlockNum-nextBlockNum+1, nextBlockNum, latestBlockNum) - - for nextBlockNum <= latestBlockNum { - ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), nextBlockNum) - if err != nil { - return err - } - // TODO: Check that the obtianed ethBlock.ParentHash == prevEthBlock.Hash; if not, reorg! - - // TODO: Send the ethHash in rollupSync(), auctionSync() and - // wdelayerSync() and make sure they all use the same block - // hash. - - // Get data from the rollup contract - rollupData, err := s.rollupSync(nextBlockNum) - if err != nil { - return err - } - - // Get data from the auction contract - auctionData, err := s.auctionSync(nextBlockNum) - if err != nil { - return err - } + // Get data from the auction contract + auctionData, err := s.auctionSync(ethBlock) + if err != nil { + return nil, nil, err + } - // Get data from the WithdrawalDelayer contract - wdelayerData, err := s.wdelayerSync(nextBlockNum) - if err != nil { - return err - } + // Get data from the WithdrawalDelayer contract + wdelayerData, err := s.wdelayerSync(ethBlock) + if err != nil { + return nil, nil, err + } - // Group all the block data into the structs to save into HistoryDB - var blockData historydb.BlockData + // Group all the block data into the structs to save into HistoryDB + var blockData common.BlockData - blockData.Block = ethBlock + blockData.Block = *ethBlock - if rollupData != nil { - blockData.L1UserTxs = rollupData.l1UserTxs - blockData.Batches = rollupData.batches - // blockData.withdrawals = rollupData.withdrawals // TODO - blockData.RegisteredTokens = rollupData.registeredTokens - blockData.RollupVars = rollupData.vars - } + blockData.L1UserTxs = rollupData.l1UserTxs + blockData.Batches = rollupData.batches + // blockData.withdrawals = rollupData.withdrawals // TODO + blockData.AddedTokens = rollupData.addTokens + blockData.RollupVars = rollupData.vars - if auctionData != nil { - blockData.Bids = auctionData.bids - blockData.Coordinators = auctionData.coordinators - blockData.AuctionVars = auctionData.vars - } + blockData.Bids = auctionData.bids + blockData.Coordinators = auctionData.coordinators + blockData.AuctionVars = auctionData.vars - if wdelayerData != nil { - blockData.WithdrawDelayerVars = wdelayerData.vars - } + blockData.WithdrawDelayerVars = wdelayerData.vars - // Add rollupData and auctionData once the method is updated - // TODO: Save Whole Struct -> AddBlockSCData(blockData) - log.Debugw("Sync()", "block", blockData) - // err = s.historyDB.AddBlock(blockData.Block) - // if err != nil { - // return err - // } - err = s.historyDB.AddBlockSCData(&blockData) - if err != nil { - return err - } - nextBlockNum++ + // log.Debugw("Sync()", "block", blockData) + // err = s.historyDB.AddBlock(blockData.Block) + // if err != nil { + // return err + // } + err = s.historyDB.AddBlockSCData(&blockData) + if err != nil { + return nil, nil, err } - return nil + return &blockData, nil, nil } // reorg manages a reorg, updating History and State DB as needed. Keeps @@ -250,12 +205,8 @@ func (s *Synchronizer) Sync(ctx context.Context) error { func (s *Synchronizer) reorg(uncleBlock *common.Block) (int64, error) { var block *common.Block blockNum := uncleBlock.EthBlockNum - found := false - - log.Debugf("Reorg first uncle block: %v", blockNum) - // Iterate History DB and the blokchain looking for the latest valid block - for !found && blockNum > s.firstSavedBlock.EthBlockNum { + for blockNum >= s.auctionConstants.GenesisBlockNum { ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), blockNum) if err != nil { return 0, err @@ -266,39 +217,36 @@ func (s *Synchronizer) reorg(uncleBlock *common.Block) (int64, error) { return 0, err } if block.Hash == ethBlock.Hash { - found = true log.Debugf("Found valid block: %v", blockNum) - } else { - log.Debugf("Discarding block: %v", blockNum) + break } - blockNum-- } + total := uncleBlock.EthBlockNum - blockNum + log.Debugw("Discarding blocks", "total", total, "from", uncleBlock.EthBlockNum, "to", blockNum+1) - if found { - // Set History DB and State DB to the correct state - err := s.historyDB.Reorg(block.EthBlockNum) - if err != nil { - return 0, err - } + // Set History DB and State DB to the correct state + err := s.historyDB.Reorg(block.EthBlockNum) + if err != nil { + return 0, err + } - batchNum, err := s.historyDB.GetLastBatchNum() - if err != nil && err != sql.ErrNoRows { + batchNum, err := s.historyDB.GetLastBatchNum() + if err != nil && err != sql.ErrNoRows { + return 0, err + } + if batchNum != 0 { + err = s.stateDB.Reset(batchNum) + if err != nil { return 0, err } - if batchNum != 0 { - err = s.stateDB.Reset(batchNum) - if err != nil { - return 0, err - } - } - - return block.EthBlockNum, nil } - return 0, ErrNotAbleToSync + return block.EthBlockNum, nil } +// TODO: Figure out who will use the Status output, and only return what's strictly need +/* // Status returns current status values from the Synchronizer func (s *Synchronizer) Status() (*common.SyncStatus, error) { // Avoid possible inconsistencies @@ -340,21 +288,27 @@ func (s *Synchronizer) Status() (*common.SyncStatus, error) { status.Synchronized = status.CurrentBlock == latestBlockNum return status, nil } +*/ -// rollupSync gets information from the Rollup Contract -func (s *Synchronizer) rollupSync(blockNum int64) (*rollupData, error) { +// rollupSync retreives all the Rollup Smart Contract Data that happened at +// ethBlock.blockNum with ethBlock.Hash. +func (s *Synchronizer) rollupSync(ethBlock *common.Block) (*rollupData, error) { + blockNum := ethBlock.EthBlockNum var rollupData = newRollupData() // var forgeL1TxsNum int64 var numAccounts int - // Get rollup events in the block - rollupEvents, _, err := s.ethClient.RollupEventsByBlock(blockNum) + // Get rollup events in the block, and make sure the block hash matches + // the expected one. + rollupEvents, blockHash, err := s.ethClient.RollupEventsByBlock(blockNum) if err != nil { return nil, err } + if *blockHash != ethBlock.Hash { + return nil, eth.ErrBlockHashMismatchEvent + } - // TODO: Replace GetLastL1TxsNum by GetNextL1TxsNum - var nextForgeL1TxsNum int64 + var nextForgeL1TxsNum int64 // forgeL1TxsNum for the next L1Batch nextForgeL1TxsNumPtr, err := s.historyDB.GetLastL1TxsNum() if err != nil { return nil, err @@ -379,7 +333,7 @@ func (s *Synchronizer) rollupSync(blockNum int64) (*rollupData, error) { // Get ForgeBatch events to get the L1CoordinatorTxs for _, evtForgeBatch := range rollupEvents.ForgeBatch { - batchData := historydb.NewBatchData() + batchData := common.NewBatchData() position := 0 // Get the input for each Tx @@ -387,70 +341,70 @@ func (s *Synchronizer) rollupSync(blockNum int64) (*rollupData, error) { if err != nil { return nil, err } + + batchNum := common.BatchNum(evtForgeBatch.BatchNum) forgeL1TxsNum := nextForgeL1TxsNum + var l1UserTxs []common.L1Tx // Check if this is a L1Batch to get L1 Tx from it if forgeBatchArgs.L1Batch { - // Get L1 User Txs from History DB - // TODO: Get L1TX from HistoryDB filtered by toforgeL1txNum & fromidx = 0 and - // update batch number and add accounts to createdAccounts updating idx - - // l1UserTxs, err := s.historyDB.GetL1UserTxs(nextForgeL1TxsNum) - // If HistoryDB doesn't have L1UserTxs at - // nextForgeL1TxsNum, check if they exist in - // rollupData.l1Txs. This could happen because in a - // block there could be multiple batches with L1Batch = - // true (although it's a very rare case). If the - // L1UserTxs are not in rollupData.l1Txs, use an empty - // array (this happens when the L1UserTxs queue is - // frozen but didn't store any tx). - l1UserTxs := []common.L1Tx{} + // Get L1UserTxs with toForgeL1TxsNum, which correspond + // to the L1UserTxs that are forged in this batch, so + // that stateDB can process them. + + // First try to find them in HistoryDB. + l1UserTxs, err := s.historyDB.GetL1UserTxs(forgeL1TxsNum) + if len(l1UserTxs) == 0 { + // If not found in the DB, try to find them in + // this block. This could happen because in a + // block there could be multiple batches with + // L1Batch = true (although it's a very rare + // case). + // If not found in the DB and the block doesn't + // contain the l1UserTxs, it means that the + // L1UserTxs queue with toForgeL1TxsNum was + // closed empty, so we leave `l1UserTxs` as an + // empty slice. + for _, l1UserTx := range rollupData.l1UserTxs { + if *l1UserTx.ToForgeL1TxsNum == forgeL1TxsNum { + l1UserTxs = append(l1UserTxs, l1UserTx) + } + } + } + if err != nil { + return nil, err + } + position = len(l1UserTxs) // Get L1 Coordinator Txs - for i := 0; i < len(forgeBatchArgs.L1CoordinatorTxs); i++ { + for i := range forgeBatchArgs.L1CoordinatorTxs { l1CoordinatorTx := forgeBatchArgs.L1CoordinatorTxs[i] l1CoordinatorTx.Position = position l1CoordinatorTx.ToForgeL1TxsNum = &forgeL1TxsNum l1CoordinatorTx.UserOrigin = false l1CoordinatorTx.EthBlockNum = blockNum - bn := new(common.BatchNum) - *bn = common.BatchNum(evtForgeBatch.BatchNum) - l1CoordinatorTx.BatchNum = bn + l1CoordinatorTx.BatchNum = &batchNum l1Tx, err := common.NewL1Tx(&l1CoordinatorTx) if err != nil { return nil, err } batchData.L1CoordinatorTxs = append(batchData.L1CoordinatorTxs, *l1Tx) - - // Check if we have to register an account - // if l1CoordinatorTx.FromIdx == 0 { - // account := common.Account{ - // // TODO: Uncommnent when common.account has IDx - // // IDx: common.Idx(idx), - // TokenID: l1CoordinatorTx.TokenID, - // Nonce: 0, - // Balance: l1CoordinatorTx.LoadAmount, - // PublicKey: l1CoordinatorTx.FromBJJ, - // EthAddr: l1CoordinatorTx.FromEthAddr, - // } - // idx++ - // batchData.createdAccounts = append(batchData.createdAccounts, &account) - // numAccounts++ - // } position++ } nextForgeL1TxsNum++ } - // Get L2Txs - poolL2Txs := common.L2TxsToPoolL2Txs(forgeBatchArgs.L2TxsData) // TODO: This is a big uggly, find a better way + // Insert all the txs forged in this batch (l1UserTxs, + // L1CoordinatorTxs, PoolL2Txs) into stateDB so that they are + // processed. + poolL2Txs := common.L2TxsToPoolL2Txs(forgeBatchArgs.L2TxsData) // TODO: This is a big ugly, find a better way - // Get exitTree // TODO: Get createdAccounts from ProcessTxs() // TODO: Get CollectedFees from ProcessTxs() // TODO: Pass forgeBatchArgs.FeeIdxCoordinator to ProcessTxs() - _, exitInfo, err := s.stateDB.ProcessTxs(batchData.L1UserTxs, batchData.L1CoordinatorTxs, poolL2Txs) + // ProcessTxs updates poolL2Txs adding: Nonce, TokenID + _, exitInfo, err := s.stateDB.ProcessTxs(l1UserTxs, batchData.L1CoordinatorTxs, poolL2Txs) if err != nil { return nil, err } @@ -459,21 +413,42 @@ func (s *Synchronizer) rollupSync(blockNum int64) (*rollupData, error) { if err != nil { return nil, err } - batchData.L2Txs = append(batchData.L2Txs, l2Txs...) + + for i := range l2Txs { + _l2Tx := l2Txs[i] + _l2Tx.Position = position + _l2Tx.EthBlockNum = blockNum + _l2Tx.BatchNum = batchNum + l2Tx, err := common.NewL2Tx(&_l2Tx) + if err != nil { + return nil, err + } + + batchData.L2Txs = append(batchData.L2Txs, *l2Tx) + position++ + } batchData.ExitTree = exitInfo + slotNum := int64(0) + if ethBlock.EthBlockNum >= s.auctionConstants.GenesisBlockNum { + slotNum = (ethBlock.EthBlockNum - s.auctionConstants.GenesisBlockNum) / + int64(s.auctionConstants.BlocksPerSlot) + } + // Get Batch information - batch := &common.Batch{ - BatchNum: common.BatchNum(evtForgeBatch.BatchNum), + batch := common.Batch{ + BatchNum: batchNum, EthBlockNum: blockNum, ForgerAddr: *sender, // CollectedFees: , TODO: Clarify where to get them if they are still needed - StateRoot: common.Hash(forgeBatchArgs.NewStRoot.Bytes()), - NumAccounts: numAccounts, - ExitRoot: common.Hash(forgeBatchArgs.NewExitRoot.Bytes()), - ForgeL1TxsNum: &forgeL1TxsNum, - // SlotNum: TODO: Calculate once ethClient provides the info // calculate from blockNum + ethClient Constants + StateRoot: forgeBatchArgs.NewStRoot, + NumAccounts: numAccounts, + ExitRoot: forgeBatchArgs.NewExitRoot, + SlotNum: slotNum, + } + if forgeBatchArgs.L1Batch { + batch.ForgeL1TxsNum = &forgeL1TxsNum } batchData.Batch = batch rollupData.batches = append(rollupData.batches, *batchData) @@ -487,12 +462,19 @@ func (s *Synchronizer) rollupSync(blockNum int64) (*rollupData, error) { token.EthAddr = evtAddToken.TokenAddress token.EthBlockNum = blockNum - // TODO: Add external information consulting SC about it using Address - token.Name = "TODO" - token.Symbol = "TODO" - token.Decimals = 8 // TODO + if consts, err := s.ethClient.EthERC20Consts(evtAddToken.TokenAddress); err != nil { + log.Warnw("Error retreiving ERC20 token constants", "addr", evtAddToken.TokenAddress) + // TODO: Add external information consulting SC about it using Address + token.Name = "ERC20_ETH_ERROR" + token.Symbol = "ERROR" + token.Decimals = 1 + } else { + token.Name = cutStringMax(consts.Name, 20) + token.Symbol = cutStringMax(consts.Symbol, 10) + token.Decimals = consts.Decimals + } - rollupData.registeredTokens = append(rollupData.registeredTokens, token) + rollupData.addTokens = append(rollupData.addTokens, token) } // TODO: rollupEvents.UpdateForgeL1L2BatchTimeout @@ -506,20 +488,31 @@ func (s *Synchronizer) rollupSync(blockNum int64) (*rollupData, error) { return &rollupData, nil } +func cutStringMax(s string, max int) string { + if len(s) > max { + return s[:max] + } + return s +} + // auctionSync gets information from the Auction Contract -func (s *Synchronizer) auctionSync(blockNum int64) (*auctionData, error) { +func (s *Synchronizer) auctionSync(ethBlock *common.Block) (*auctionData, error) { + blockNum := ethBlock.EthBlockNum var auctionData = newAuctionData() // Get auction events in the block - auctionEvents, _, err := s.ethClient.AuctionEventsByBlock(blockNum) + auctionEvents, blockHash, err := s.ethClient.AuctionEventsByBlock(blockNum) if err != nil { return nil, err } + if *blockHash != ethBlock.Hash { + return nil, eth.ErrBlockHashMismatchEvent + } // Get bids for _, evtNewBid := range auctionEvents.NewBid { bid := common.Bid{ - SlotNum: common.SlotNum(evtNewBid.Slot), + SlotNum: evtNewBid.Slot, BidValue: evtNewBid.BidAmount, Bidder: evtNewBid.Bidder, EthBlockNum: blockNum, @@ -556,11 +549,14 @@ func (s *Synchronizer) auctionSync(blockNum int64) (*auctionData, error) { } // wdelayerSync gets information from the Withdrawal Delayer Contract -func (s *Synchronizer) wdelayerSync(blockNum int64) (*wdelayerData, error) { +func (s *Synchronizer) wdelayerSync(ethBlock *common.Block) (*wdelayerData, error) { + // blockNum := ethBlock.EthBlockNum // TODO: VARS // TODO: CONSTANTS - return nil, nil + return &wdelayerData{ + vars: nil, + }, nil } // func (s *Synchronizer) getIdx(rollupEvents *eth.RollupEvents) (int64, error) { @@ -579,17 +575,15 @@ func (s *Synchronizer) wdelayerSync(blockNum int64) (*wdelayerData, error) { // } func getL1UserTx(eventsL1UserTx []eth.RollupEventL1UserTx, blockNum int64) ([]common.L1Tx, error) { - l1Txs := make([]common.L1Tx, 0) - - for _, evtL1UserTx := range eventsL1UserTx { - evtL1UserTx.L1UserTx.EthBlockNum = blockNum - nL1Tx, err := common.NewL1Tx(&evtL1UserTx.L1UserTx) + l1Txs := make([]common.L1Tx, len(eventsL1UserTx)) + for i := range eventsL1UserTx { + eventsL1UserTx[i].L1UserTx.EthBlockNum = blockNum + // Check validity of L1UserTx + l1Tx, err := common.NewL1Tx(&eventsL1UserTx[i].L1UserTx) if err != nil { return nil, err } - evtL1UserTx.L1UserTx = *nL1Tx - - l1Txs = append(l1Txs, evtL1UserTx.L1UserTx) + l1Txs[i] = *l1Tx } return l1Txs, nil } diff --git a/synchronizer/synchronizer_test.go b/synchronizer/synchronizer_test.go index 0d34044..353af78 100644 --- a/synchronizer/synchronizer_test.go +++ b/synchronizer/synchronizer_test.go @@ -2,15 +2,24 @@ package synchronizer import ( "context" + "crypto/ecdsa" + "encoding/binary" + "fmt" "io/ioutil" + "math/big" "os" "testing" ethCommon "github.com/ethereum/go-ethereum/common" + ethCrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/hermeznetwork/hermez-node/common" dbUtils "github.com/hermeznetwork/hermez-node/db" "github.com/hermeznetwork/hermez-node/db/historydb" "github.com/hermeznetwork/hermez-node/db/statedb" + "github.com/hermeznetwork/hermez-node/eth" "github.com/hermeznetwork/hermez-node/test" + "github.com/iden3/go-iden3-crypto/babyjub" + "github.com/jinzhu/copier" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -25,7 +34,14 @@ func (t *timer) Time() int64 { return currentTime } +type tokenData struct { + TokenID common.TokenID + Addr ethCommon.Address + Consts eth.ERC20Consts +} + func TestSync(t *testing.T) { + ctx := context.Background() // Int State DB dir, err := ioutil.TempDir("", "tmpdb") require.Nil(t, err) @@ -52,19 +68,20 @@ func TestSync(t *testing.T) { s, err := NewSynchronizer(client, historyDB, stateDB) require.Nil(t, err) - // Test Sync for ethereum genesis block - err = s.Sync(context.Background()) + // Test Sync for rollup genesis block + blockData, _, err := s.Sync2(ctx, nil) require.Nil(t, err) - + require.NotNil(t, blockData) + assert.Equal(t, int64(1), blockData.Block.EthBlockNum) blocks, err := s.historyDB.GetBlocks(0, 9999) require.Nil(t, err) + assert.Equal(t, 1, len(blocks)) assert.Equal(t, int64(1), blocks[0].EthBlockNum) - // TODO once Til is completed /* - // Test Sync for a block with new Tokens and L1UserTxs - // accounts := test.GenerateKeys(t, []string{"A", "B", "C", "D"}) - l1UserTxs, _, _, _ := test.GenerateTestTxsFromSet(t, ` + // Test Sync for a block with new Tokens and L1UserTxs + // accounts := test.GenerateKeys(t, []string{"A", "B", "C", "D"}) + l1UserTxs, _, _, _ := test.GenerateTestTxsFromSet(t, ` A (1): 10 A (2): 20 B (1): 5 @@ -72,7 +89,7 @@ func TestSync(t *testing.T) { D (3): 15 > advance batch `) - require.Greater(t, len(l1UserTxs[0]), 0) + require.Greater(t, len(l1UserTxs[0]), 0) // require.Greater(t, len(tokens), 0) for i := 1; i <= 3; i++ { @@ -94,6 +111,86 @@ func TestSync(t *testing.T) { assert.Equal(t, 3, len(getTokens)) */ + // Generate tokens vector + numTokens := 3 + tokens := make([]tokenData, numTokens) + for i := 1; i <= numTokens; i++ { + addr := ethCommon.BigToAddress(big.NewInt(int64(i * 10000))) + consts := eth.ERC20Consts{ + Name: fmt.Sprintf("Token %d", i), + Symbol: fmt.Sprintf("TK%d", i), + Decimals: uint64(i * 2), + } + tokens[i-1] = tokenData{common.TokenID(i), addr, consts} + } + + numUsers := 4 + keys := make([]*userKeys, numUsers) + for i := range keys { + keys[i] = genKeys(i) + } + + // Generate some L1UserTxs of type deposit + l1UserTxs := make([]*common.L1Tx, 5) + for i := range l1UserTxs { + l1UserTxs[i] = &common.L1Tx{ + FromIdx: common.Idx(0), + FromEthAddr: keys[i%numUsers].Addr, + FromBJJ: keys[i%numUsers].BJJPK, + Amount: big.NewInt(0), + LoadAmount: big.NewInt((int64(i) + 1) * 1000), + TokenID: common.TokenID(i%numTokens + 1), + } + } + + // Add tokens to ethereum, and to rollup + for _, token := range tokens { + client.CtlAddERC20(token.Addr, token.Consts) + _, err := client.RollupAddToken(token.Addr, clientSetup.RollupVariables.FeeAddToken) + require.Nil(t, err) + } + + // Add L1Txs to rollup + for i := range l1UserTxs { + tx := l1UserTxs[i] + _, err := client.RollupL1UserTxERC20ETH(tx.FromBJJ, int64(tx.FromIdx), tx.LoadAmount, tx.Amount, + uint32(tx.TokenID), int64(tx.ToIdx)) + require.Nil(t, err) + } + + // Mine block and sync + client.CtlMineBlock() + + blockData, _, err = s.Sync2(ctx, nil) + require.Nil(t, err) + require.NotNil(t, blockData) + assert.Equal(t, int64(2), blockData.Block.EthBlockNum) + + // Check tokens in DB + dbTokens, err := s.historyDB.GetAllTokens() + require.Nil(t, err) + assert.Equal(t, len(tokens), len(dbTokens)) + assert.Equal(t, len(tokens), len(blockData.AddedTokens)) + for i := range tokens { + token := tokens[i] + addToken := blockData.AddedTokens[i] + dbToken := dbTokens[i] + + assert.Equal(t, int64(2), addToken.EthBlockNum) + assert.Equal(t, token.TokenID, addToken.TokenID) + assert.Equal(t, token.Addr, addToken.EthAddr) + assert.Equal(t, token.Consts.Name, addToken.Name) + assert.Equal(t, token.Consts.Symbol, addToken.Symbol) + assert.Equal(t, token.Consts.Decimals, addToken.Decimals) + + var addTokenCpy historydb.TokenRead + require.Nil(t, copier.Copy(&addTokenCpy, &addToken)) // copy common.Token to historydb.TokenRead + addTokenCpy.ItemID = dbToken.ItemID // we don't care about ItemID + assert.Equal(t, addTokenCpy, dbToken) + } + + // Check L1UserTxs in DB + // TODO: Reorg will be properly tested once we have the mock ethClient implemented /* // Force a Reorg @@ -116,3 +213,28 @@ func TestSync(t *testing.T) { require.Nil(t, err) */ } + +type userKeys struct { + BJJSK *babyjub.PrivateKey + BJJPK *babyjub.PublicKey + Addr ethCommon.Address +} + +func genKeys(i int) *userKeys { + i++ // i = 0 doesn't work for the ecdsa key generation + var sk babyjub.PrivateKey + binary.LittleEndian.PutUint64(sk[:], uint64(i)) + + // eth address + var key ecdsa.PrivateKey + key.D = big.NewInt(int64(i)) // only for testing + key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes()) + key.Curve = ethCrypto.S256() + addr := ethCrypto.PubkeyToAddress(key.PublicKey) + + return &userKeys{ + BJJSK: &sk, + BJJPK: sk.Public(), + Addr: addr, + } +} diff --git a/test/historydb.go b/test/historydb.go index a0f5ffd..5c15ad9 100644 --- a/test/historydb.go +++ b/test/historydb.go @@ -60,11 +60,11 @@ func GenBatches(nBatches int, blocks []common.Block) []common.Batch { //nolint:gomnd ForgerAddr: ethCommon.BigToAddress(big.NewInt(6886723)), CollectedFees: collectedFees, - StateRoot: common.Hash([]byte("duhdqlwiucgwqeiu")), + StateRoot: big.NewInt(int64(i) * 5), //nolint:gomnd //nolint:gomnd NumAccounts: 30, - ExitRoot: common.Hash([]byte("tykertheuhtgenuer3iuw3b")), - SlotNum: common.SlotNum(i), + ExitRoot: big.NewInt(int64(i) * 16), //nolint:gomnd + SlotNum: int64(i), } if i%2 == 0 { toForge := new(int64) @@ -324,7 +324,7 @@ func GenBids(nBids int, blocks []common.Block, coords []common.Coordinator) []co bids := []common.Bid{} for i := 0; i < nBids; i++ { bids = append(bids, common.Bid{ - SlotNum: common.SlotNum(i), + SlotNum: int64(i), BidValue: big.NewInt(int64(i)), EthBlockNum: blocks[i%len(blocks)].EthBlockNum, Bidder: coords[i%len(blocks)].Bidder, diff --git a/test/til/README.md b/test/til/README.md index 72981e0..99231d7 100644 --- a/test/til/README.md +++ b/test/til/README.md @@ -18,8 +18,8 @@ Available instructions: ```go Type: Blockchain -// register the TokenID: -RegisterToken(1) +// add the TokenID: +AddToken(1) // deposit of TokenID=1, on the account of tokenID=1 for the user A, of an // amount of 50 units diff --git a/test/til/lang.go b/test/til/lang.go index 28b00d7..f5ebbcf 100644 --- a/test/til/lang.go +++ b/test/til/lang.go @@ -39,10 +39,10 @@ var typeNewBatchL1 common.TxType = "InstrTypeNewBatchL1" // common.TxType of a new ethereum block var typeNewBlock common.TxType = "InstrTypeNewBlock" -// typeRegisterToken is used for testing purposes only, and represents the +// typeAddToken is used for testing purposes only, and represents the // common.TxType of a new Token regsitration // It has 'nolint:gosec' as the string 'Token' triggers gosec as a potential leaked Token (which is not the case) -var typeRegisterToken common.TxType = "InstrTypeRegisterToken" //nolint:gosec +var typeAddToken common.TxType = "InstrTypeAddToken" //nolint:gosec var txTypeCreateAccountDepositCoordinator common.TxType = "TypeCreateAccountDepositCoordinator" @@ -306,7 +306,7 @@ func (p *parser) parseLine(setType setType) (*instruction, error) { } else { return c, fmt.Errorf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", lit) } - } else if lit == "RegisterToken" { + } else if lit == "AddToken" { if err := p.expectChar(c, "("); err != nil { return c, err } @@ -322,7 +322,7 @@ func (p *parser) parseLine(setType setType) (*instruction, error) { if err := p.expectChar(c, ")"); err != nil { return c, err } - c.typ = typeRegisterToken + c.typ = typeAddToken line, _ := p.s.r.ReadString('\n') c.literal += line return c, newEventLine @@ -519,8 +519,8 @@ func (p *parser) parse() (*parsedSet, error) { } instruction.lineNum = i if err == newEventLine { - if instruction.typ == typeRegisterToken && instruction.tokenID == common.TokenID(0) { - return ps, fmt.Errorf("Line %d: RegisterToken can not register TokenID 0", i) + if instruction.typ == typeAddToken && instruction.tokenID == common.TokenID(0) { + return ps, fmt.Errorf("Line %d: AddToken can not register TokenID 0", i) } ps.instructions = append(ps.instructions, *instruction) continue diff --git a/test/til/lang_test.go b/test/til/lang_test.go index edbae8d..ae57518 100644 --- a/test/til/lang_test.go +++ b/test/til/lang_test.go @@ -16,8 +16,8 @@ func TestParseBlockchainTxs(t *testing.T) { Type: Blockchain // token registrations - RegisterToken(1) - RegisterToken(2) + AddToken(1) + AddToken(2) // deposits Deposit(1) A: 10 @@ -34,7 +34,7 @@ func TestParseBlockchainTxs(t *testing.T) { // set new batch > batch - RegisterToken(3) + AddToken(3) DepositTransfer(1) A-B: 15, 10 (1) Transfer(1) C-A : 3 (1) @@ -121,7 +121,7 @@ func TestParseErrors(t *testing.T) { s = ` Type: Blockchain - RegisterToken(1) + AddToken(1) Deposit(1) A: 10 20 ` parser = newParser(strings.NewReader(s)) @@ -146,7 +146,7 @@ func TestParseErrors(t *testing.T) { s = ` Type: Blockchain - RegisterToken(1) + AddToken(1) Transfer(1) A-B: 10 (255) ` parser = newParser(strings.NewReader(s)) @@ -206,10 +206,10 @@ func TestParseErrors(t *testing.T) { assert.Equal(t, "Line 2: Instruction of 'Type: Blockchain' when there is already a previous instruction 'Type: PoolL2' defined", err.Error()) s = `Type: Blockchain - RegisterToken(1) - RegisterToken(0) + AddToken(1) + AddToken(0) ` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "Line 3: RegisterToken can not register TokenID 0", err.Error()) + assert.Equal(t, "Line 3: AddToken can not register TokenID 0", err.Error()) } diff --git a/test/til/sets.go b/test/til/sets.go index 4cf5c0f..a3cba57 100644 --- a/test/til/sets.go +++ b/test/til/sets.go @@ -6,9 +6,9 @@ package til var SetBlockchain0 = ` // Set containing Blockchain transactions Type: Blockchain -RegisterToken(1) -RegisterToken(2) -RegisterToken(3) +AddToken(1) +AddToken(2) +AddToken(3) // deposits TokenID: 1 CreateAccountDeposit(1) A: 50 diff --git a/test/til/txs.go b/test/til/txs.go index eb97906..2cea702 100644 --- a/test/til/txs.go +++ b/test/til/txs.go @@ -26,17 +26,22 @@ type Context struct { // rollupConstMaxL1UserTx Maximum L1-user transactions allowed to be queued in a batch rollupConstMaxL1UserTx int - idx int - currBlock BlockData - currBatch BatchData - currBatchNum int - queues [][]L1Tx - toForgeNum int - openToForge int + idx int + currBlock common.BlockData + currBatch common.BatchData + currBatchNum int + queues [][]L1Tx + toForgeNum int + openToForge int + currBatchTest struct { + l1CoordinatorTxs []L1Tx + l2Txs []L2Tx + } } // NewContext returns a new Context func NewContext(rollupConstMaxL1UserTx int) *Context { + currBatchNum := 1 // The protocol defines the first batchNum to be 1 return &Context{ Users: make(map[string]*User), l1CreatedAccounts: make(map[string]*Account), @@ -44,7 +49,12 @@ func NewContext(rollupConstMaxL1UserTx int) *Context { rollupConstMaxL1UserTx: rollupConstMaxL1UserTx, idx: common.UserThreshold, - currBatchNum: 0, + // We use some placeholder values for StateRoot and ExitTree + // because these values will never be nil + currBatch: common.BatchData{Batch: common.Batch{ + BatchNum: common.BatchNum(currBatchNum), + StateRoot: big.NewInt(0), ExitRoot: big.NewInt(0)}}, + currBatchNum: currBatchNum, // start with 2 queues, one for toForge, and the other for openToForge queues: make([][]L1Tx, 2), toForgeNum: 0, @@ -65,26 +75,6 @@ type User struct { Accounts map[common.TokenID]*Account } -// BlockData contains the information of a Block -type BlockData struct { - // block *common.Block // ethereum block - // L1UserTxs that were accepted in the block - L1UserTxs []common.L1Tx - Batches []BatchData - RegisteredTokens []common.Token -} - -// BatchData contains the information of a Batch -type BatchData struct { - L1Batch bool // TODO: Remove once Batch.ForgeL1TxsNum is a pointer - L1CoordinatorTxs []common.L1Tx - testL1CoordinatorTxs []L1Tx - L2Txs []common.L2Tx - // testL2Tx are L2Txs without the Idx&EthAddr&BJJ setted, but with the - // string that represents the account - testL2Txs []L2Tx -} - // L1Tx is the data structure used internally for transaction test generation, // which contains a common.L1Tx data plus some intermediate data for the // transaction generation. @@ -109,7 +99,7 @@ type L2Tx struct { // GenerateBlocks returns an array of BlockData for a given set. It uses the // accounts (keys & nonces) of the Context. -func (tc *Context) GenerateBlocks(set string) ([]BlockData, error) { +func (tc *Context) GenerateBlocks(set string) ([]common.BlockData, error) { parser := newParser(strings.NewReader(set)) parsedSet, err := parser.parse() if err != nil { @@ -121,7 +111,7 @@ func (tc *Context) GenerateBlocks(set string) ([]BlockData, error) { tc.generateKeys(tc.accountsNames) - var blocks []BlockData + var blocks []common.BlockData for _, inst := range parsedSet.instructions { switch inst.typ { case txTypeCreateAccountDepositCoordinator: @@ -133,6 +123,7 @@ func (tc *Context) GenerateBlocks(set string) ([]BlockData, error) { FromEthAddr: tc.Users[inst.from].Addr, FromBJJ: tc.Users[inst.from].BJJ.Public(), TokenID: inst.tokenID, + Amount: big.NewInt(0), LoadAmount: big.NewInt(int64(inst.loadAmount)), Type: common.TxTypeCreateAccountDeposit, // as txTypeCreateAccountDepositCoordinator is not valid oustide Til package } @@ -141,7 +132,7 @@ func (tc *Context) GenerateBlocks(set string) ([]BlockData, error) { fromIdxName: inst.from, L1Tx: tx, } - tc.currBatch.testL1CoordinatorTxs = append(tc.currBatch.testL1CoordinatorTxs, testTx) + tc.currBatchTest.l1CoordinatorTxs = append(tc.currBatchTest.l1CoordinatorTxs, testTx) case common.TxTypeCreateAccountDeposit, common.TxTypeCreateAccountDepositTransfer: if err := tc.checkIfTokenIsRegistered(inst); err != nil { log.Error(err) @@ -151,6 +142,7 @@ func (tc *Context) GenerateBlocks(set string) ([]BlockData, error) { FromEthAddr: tc.Users[inst.from].Addr, FromBJJ: tc.Users[inst.from].BJJ.Public(), TokenID: inst.tokenID, + Amount: big.NewInt(0), LoadAmount: big.NewInt(int64(inst.loadAmount)), Type: inst.typ, } @@ -175,6 +167,7 @@ func (tc *Context) GenerateBlocks(set string) ([]BlockData, error) { } tx := common.L1Tx{ TokenID: inst.tokenID, + Amount: big.NewInt(0), LoadAmount: big.NewInt(int64(inst.loadAmount)), Type: inst.typ, } @@ -206,7 +199,7 @@ func (tc *Context) GenerateBlocks(set string) ([]BlockData, error) { tokenID: inst.tokenID, L2Tx: tx, } - tc.currBatch.testL2Txs = append(tc.currBatch.testL2Txs, testTx) + tc.currBatchTest.l2Txs = append(tc.currBatchTest.l2Txs, testTx) case common.TxTypeExit: if err := tc.checkIfTokenIsRegistered(inst); err != nil { log.Error(err) @@ -225,17 +218,18 @@ func (tc *Context) GenerateBlocks(set string) ([]BlockData, error) { tokenID: inst.tokenID, L2Tx: tx, } - tc.currBatch.testL2Txs = append(tc.currBatch.testL2Txs, testTx) + tc.currBatchTest.l2Txs = append(tc.currBatchTest.l2Txs, testTx) case common.TxTypeForceExit: if err := tc.checkIfTokenIsRegistered(inst); err != nil { log.Error(err) return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } tx := common.L1Tx{ - ToIdx: common.Idx(1), // as is an Exit - TokenID: inst.tokenID, - Amount: big.NewInt(int64(inst.amount)), - Type: common.TxTypeExit, + ToIdx: common.Idx(1), // as is an Exit + TokenID: inst.tokenID, + Amount: big.NewInt(int64(inst.amount)), + LoadAmount: big.NewInt(0), + Type: common.TxTypeExit, } testTx := L1Tx{ lineNum: inst.lineNum, @@ -245,7 +239,7 @@ func (tc *Context) GenerateBlocks(set string) ([]BlockData, error) { } tc.addToL1Queue(testTx) case typeNewBatch: - if err = tc.calculateIdxForL1Txs(true, tc.currBatch.testL1CoordinatorTxs); err != nil { + if err = tc.calculateIdxForL1Txs(true, tc.currBatchTest.l1CoordinatorTxs); err != nil { return nil, err } if err = tc.setIdxs(); err != nil { @@ -257,7 +251,7 @@ func (tc *Context) GenerateBlocks(set string) ([]BlockData, error) { if err = tc.calculateIdxForL1Txs(false, tc.queues[tc.toForgeNum]); err != nil { return nil, err } - if err = tc.calculateIdxForL1Txs(true, tc.currBatch.testL1CoordinatorTxs); err != nil { + if err = tc.calculateIdxForL1Txs(true, tc.currBatchTest.l1CoordinatorTxs); err != nil { return nil, err } @@ -277,8 +271,8 @@ func (tc *Context) GenerateBlocks(set string) ([]BlockData, error) { if testTx.L1Tx.Type == common.TxTypeExit { testTx.L1Tx.ToIdx = common.Idx(1) } - bn := common.BatchNum(tc.currBatchNum) - testTx.L1Tx.BatchNum = &bn + // bn := common.BatchNum(tc.currBatchNum) + // testTx.L1Tx.BatchNum = &bn nTx, err := common.NewL1Tx(&testTx.L1Tx) if err != nil { fmt.Println(testTx) @@ -301,17 +295,17 @@ func (tc *Context) GenerateBlocks(set string) ([]BlockData, error) { } case typeNewBlock: blocks = append(blocks, tc.currBlock) - tc.currBlock = BlockData{} - case typeRegisterToken: + tc.currBlock = common.BlockData{} + case typeAddToken: newToken := common.Token{ TokenID: inst.tokenID, EthBlockNum: int64(len(blocks)), } if inst.tokenID != tc.lastRegisteredTokenID+1 { - return nil, fmt.Errorf("Line %d: RegisterToken TokenID should be sequential, expected TokenID: %d, defined TokenID: %d", inst.lineNum, tc.lastRegisteredTokenID+1, inst.tokenID) + return nil, fmt.Errorf("Line %d: AddToken TokenID should be sequential, expected TokenID: %d, defined TokenID: %d", inst.lineNum, tc.lastRegisteredTokenID+1, inst.tokenID) } tc.lastRegisteredTokenID++ - tc.currBlock.RegisteredTokens = append(tc.currBlock.RegisteredTokens, newToken) + tc.currBlock.AddedTokens = append(tc.currBlock.AddedTokens, newToken) default: return nil, fmt.Errorf("Line %d: Unexpected type: %s", inst.lineNum, inst.typ) } @@ -347,8 +341,8 @@ func (tc *Context) calculateIdxForL1Txs(isCoordinatorTxs bool, txs []L1Tx) error // setIdxs sets the Idxs to the transactions of the tc.currBatch func (tc *Context) setIdxs() error { // once Idxs are calculated, update transactions to use the new Idxs - for i := 0; i < len(tc.currBatch.testL2Txs); i++ { - testTx := &tc.currBatch.testL2Txs[i] + for i := 0; i < len(tc.currBatchTest.l2Txs); i++ { + testTx := &tc.currBatchTest.l2Txs[i] if tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID] == nil { return fmt.Errorf("Line %d: %s from User %s for TokenID %d while account not created yet", testTx.lineNum, testTx.L2Tx.Type, testTx.fromIdxName, testTx.tokenID) @@ -380,8 +374,10 @@ func (tc *Context) setIdxs() error { tc.currBlock.Batches = append(tc.currBlock.Batches, tc.currBatch) tc.currBatchNum++ - tc.currBatch = BatchData{} - + tc.currBatch = common.BatchData{Batch: tc.currBatch.Batch} + tc.currBatch.Batch.BatchNum = common.BatchNum(tc.currBatchNum) + tc.currBatchTest.l1CoordinatorTxs = nil + tc.currBatchTest.l2Txs = nil return nil } @@ -394,6 +390,9 @@ func (tc *Context) addToL1Queue(tx L1Tx) { newQueue := []L1Tx{} tc.queues = append(tc.queues, newQueue) } + tx.L1Tx.UserOrigin = true + toForgeL1TxsNum := int64(tc.openToForge) + tx.L1Tx.ToForgeL1TxsNum = &toForgeL1TxsNum tc.queues[tc.openToForge] = append(tc.queues[tc.openToForge], tx) } diff --git a/test/til/txs_test.go b/test/til/txs_test.go index 66be038..b898d04 100644 --- a/test/til/txs_test.go +++ b/test/til/txs_test.go @@ -13,9 +13,9 @@ import ( func TestGenerateBlocks(t *testing.T) { set := ` Type: Blockchain - RegisterToken(1) - RegisterToken(2) - RegisterToken(3) + AddToken(1) + AddToken(2) + AddToken(3) CreateAccountDeposit(1) A: 10 CreateAccountDeposit(2) A: 20 @@ -23,15 +23,15 @@ func TestGenerateBlocks(t *testing.T) { CreateAccountDeposit(1) C: 5 CreateAccountDepositTransfer(1) D-A: 15, 10 (3) - > batchL1 - > batchL1 + > batchL1 // batchNum = 1 + > batchL1 // batchNum = 2 Transfer(1) A-B: 6 (1) Transfer(1) B-D: 3 (1) Transfer(1) A-D: 1 (1) // set new batch - > batch + > batch // batchNum = 3 CreateAccountDepositCoordinator(1) E CreateAccountDepositCoordinator(2) B @@ -44,12 +44,12 @@ func TestGenerateBlocks(t *testing.T) { CreateAccountDeposit(3) User1: 20 CreateAccountDepositCoordinator(1) User1 CreateAccountDepositCoordinator(3) User0 - > batchL1 + > batchL1 // batchNum = 4 Transfer(1) User0-User1: 15 (1) Transfer(3) User1-User0: 15 (1) Transfer(1) A-C: 1 (1) - > batchL1 + > batchL1 // batchNum = 5 Transfer(1) User1-User0: 1 (1) @@ -59,7 +59,7 @@ func TestGenerateBlocks(t *testing.T) { Transfer(1) A-B: 1 (1) Exit(1) A: 5 - > batch + > batch // batchNum = 6 > block // this transaction should not be generated, as it's after last @@ -88,34 +88,34 @@ func TestGenerateBlocks(t *testing.T) { // // #4: CreateAccountDepositTransfer(1) D-A: 15, 10 (3) tc.checkL1TxParams(t, blocks[0].L1UserTxs[4], common.TxTypeCreateAccountDepositTransfer, 1, "D", "A", big.NewInt(15), big.NewInt(10)) // #5: Transfer(1) A-B: 6 (1) - tc.checkL2TxParams(t, blocks[0].Batches[2].L2Txs[0], common.TxTypeTransfer, 1, "A", "B", big.NewInt(6), common.BatchNum(2), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[0].Batches[2].L2Txs[0], common.TxTypeTransfer, 1, "A", "B", big.NewInt(6), common.BatchNum(3), common.Nonce(1)) // #6: Transfer(1) B-D: 3 (1) - tc.checkL2TxParams(t, blocks[0].Batches[2].L2Txs[1], common.TxTypeTransfer, 1, "B", "D", big.NewInt(3), common.BatchNum(2), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[0].Batches[2].L2Txs[1], common.TxTypeTransfer, 1, "B", "D", big.NewInt(3), common.BatchNum(3), common.Nonce(1)) // #7: Transfer(1) A-D: 1 (1) - tc.checkL2TxParams(t, blocks[0].Batches[2].L2Txs[2], common.TxTypeTransfer, 1, "A", "D", big.NewInt(1), common.BatchNum(2), common.Nonce(2)) + tc.checkL2TxParams(t, blocks[0].Batches[2].L2Txs[2], common.TxTypeTransfer, 1, "A", "D", big.NewInt(1), common.BatchNum(3), common.Nonce(2)) // change of Batch // #8: DepositTransfer(1) A-B: 15, 10 (1) tc.checkL1TxParams(t, blocks[0].L1UserTxs[5], common.TxTypeDepositTransfer, 1, "A", "B", big.NewInt(15), big.NewInt(10)) // #10: Transfer(1) C-A : 3 (1) - tc.checkL2TxParams(t, blocks[0].Batches[3].L2Txs[0], common.TxTypeTransfer, 1, "C", "A", big.NewInt(3), common.BatchNum(3), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[0].Batches[3].L2Txs[0], common.TxTypeTransfer, 1, "C", "A", big.NewInt(3), common.BatchNum(4), common.Nonce(1)) // #11: Transfer(2) A-B: 15 (1) - tc.checkL2TxParams(t, blocks[0].Batches[3].L2Txs[1], common.TxTypeTransfer, 2, "A", "B", big.NewInt(15), common.BatchNum(3), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[0].Batches[3].L2Txs[1], common.TxTypeTransfer, 2, "A", "B", big.NewInt(15), common.BatchNum(4), common.Nonce(1)) // #12: Deposit(1) User0: 20 tc.checkL1TxParams(t, blocks[0].L1UserTxs[6], common.TxTypeCreateAccountDeposit, 1, "User0", "", big.NewInt(20), nil) // // #13: Deposit(3) User1: 20 tc.checkL1TxParams(t, blocks[0].L1UserTxs[7], common.TxTypeCreateAccountDeposit, 3, "User1", "", big.NewInt(20), nil) // #14: Transfer(1) User0-User1: 15 (1) - tc.checkL2TxParams(t, blocks[0].Batches[4].L2Txs[0], common.TxTypeTransfer, 1, "User0", "User1", big.NewInt(15), common.BatchNum(4), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[0].Batches[4].L2Txs[0], common.TxTypeTransfer, 1, "User0", "User1", big.NewInt(15), common.BatchNum(5), common.Nonce(1)) // #15: Transfer(3) User1-User0: 15 (1) - tc.checkL2TxParams(t, blocks[0].Batches[4].L2Txs[1], common.TxTypeTransfer, 3, "User1", "User0", big.NewInt(15), common.BatchNum(4), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[0].Batches[4].L2Txs[1], common.TxTypeTransfer, 3, "User1", "User0", big.NewInt(15), common.BatchNum(5), common.Nonce(1)) // #16: Transfer(1) A-C: 1 (1) - tc.checkL2TxParams(t, blocks[0].Batches[4].L2Txs[2], common.TxTypeTransfer, 1, "A", "C", big.NewInt(1), common.BatchNum(4), common.Nonce(4)) + tc.checkL2TxParams(t, blocks[0].Batches[4].L2Txs[2], common.TxTypeTransfer, 1, "A", "C", big.NewInt(1), common.BatchNum(5), common.Nonce(4)) // change of Batch // #17: Transfer(1) User1-User0: 1 (1) - tc.checkL2TxParams(t, blocks[1].Batches[0].L2Txs[0], common.TxTypeTransfer, 1, "User1", "User0", big.NewInt(1), common.BatchNum(5), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[1].Batches[0].L2Txs[0], common.TxTypeTransfer, 1, "User1", "User0", big.NewInt(1), common.BatchNum(6), common.Nonce(1)) // change of Block (implies also a change of batch) // #18: Transfer(1) A-B: 1 (1) - tc.checkL2TxParams(t, blocks[1].Batches[0].L2Txs[1], common.TxTypeTransfer, 1, "A", "B", big.NewInt(1), common.BatchNum(5), common.Nonce(5)) + tc.checkL2TxParams(t, blocks[1].Batches[0].L2Txs[1], common.TxTypeTransfer, 1, "A", "B", big.NewInt(1), common.BatchNum(6), common.Nonce(5)) } func (tc *Context) checkL1TxParams(t *testing.T, tx common.L1Tx, typ common.TxType, tokenID common.TokenID, from, to string, loadAmount, amount *big.Int) { @@ -151,9 +151,9 @@ func (tc *Context) checkL2TxParams(t *testing.T, tx common.L2Tx, typ common.TxTy func TestGeneratePoolL2Txs(t *testing.T) { set := ` Type: Blockchain - RegisterToken(1) - RegisterToken(2) - RegisterToken(3) + AddToken(1) + AddToken(2) + AddToken(3) CreateAccountDeposit(1) A: 10 CreateAccountDeposit(2) A: 20 @@ -221,38 +221,38 @@ func TestGenerateErrors(t *testing.T) { _, err := tc.GenerateBlocks(set) assert.Equal(t, "Line 2: Can not process CreateAccountDeposit: TokenID 1 not registered, last registered TokenID: 0", err.Error()) - // ensure RegisterToken sequentiality and not using 0 + // ensure AddToken sequentiality and not using 0 set = ` Type: Blockchain - RegisterToken(0) + AddToken(0) ` tc = NewContext(eth.RollupConstMaxL1UserTx) _, err = tc.GenerateBlocks(set) - require.Equal(t, "Line 2: RegisterToken can not register TokenID 0", err.Error()) + require.Equal(t, "Line 2: AddToken can not register TokenID 0", err.Error()) set = ` Type: Blockchain - RegisterToken(2) + AddToken(2) ` tc = NewContext(eth.RollupConstMaxL1UserTx) _, err = tc.GenerateBlocks(set) - require.Equal(t, "Line 2: RegisterToken TokenID should be sequential, expected TokenID: 1, defined TokenID: 2", err.Error()) + require.Equal(t, "Line 2: AddToken TokenID should be sequential, expected TokenID: 1, defined TokenID: 2", err.Error()) set = ` Type: Blockchain - RegisterToken(1) - RegisterToken(2) - RegisterToken(3) - RegisterToken(5) + AddToken(1) + AddToken(2) + AddToken(3) + AddToken(5) ` tc = NewContext(eth.RollupConstMaxL1UserTx) _, err = tc.GenerateBlocks(set) - require.Equal(t, "Line 5: RegisterToken TokenID should be sequential, expected TokenID: 4, defined TokenID: 5", err.Error()) + require.Equal(t, "Line 5: AddToken TokenID should be sequential, expected TokenID: 4, defined TokenID: 5", err.Error()) // check transactions when account is not created yet set = ` Type: Blockchain - RegisterToken(1) + AddToken(1) CreateAccountDeposit(1) A: 10 > batchL1 CreateAccountDeposit(1) B @@ -264,7 +264,7 @@ func TestGenerateErrors(t *testing.T) { require.Equal(t, "Line 5: CreateAccountDeposit(1)BTransfer(1) A-B: 6 (1)\n, err: Expected ':', found 'Transfer'", err.Error()) set = ` Type: Blockchain - RegisterToken(1) + AddToken(1) CreateAccountDeposit(1) A: 10 > batchL1 CreateAccountDepositCoordinator(1) B @@ -280,7 +280,7 @@ func TestGenerateErrors(t *testing.T) { // check nonces set = ` Type: Blockchain - RegisterToken(1) + AddToken(1) CreateAccountDeposit(1) A: 10 > batchL1 CreateAccountDepositCoordinator(1) B