Browse Source

Test synchronizer reorg

feature/sql-semaphore1
Eduard S 3 years ago
parent
commit
457ff94bfa
6 changed files with 191 additions and 76 deletions
  1. +2
    -2
      db/historydb/historydb.go
  2. +1
    -1
      db/historydb/historydb_test.go
  3. +10
    -1
      db/statedb/statedb.go
  4. +6
    -5
      synchronizer/synchronizer.go
  5. +165
    -65
      synchronizer/synchronizer_test.go
  6. +7
    -2
      test/ethclient.go

+ 2
- 2
db/historydb/historydb.go

@ -729,8 +729,8 @@ func (hdb *HistoryDB) addAccounts(d meddler.DB, accounts []common.Account) error
) )
} }
// GetAccounts returns a list of accounts from the DB
func (hdb *HistoryDB) GetAccounts() ([]common.Account, error) {
// GetAllAccounts returns a list of accounts from the DB
func (hdb *HistoryDB) GetAllAccounts() ([]common.Account, error) {
var accs []*common.Account var accs []*common.Account
err := meddler.QueryAll( err := meddler.QueryAll(
hdb.db, &accs, hdb.db, &accs,

+ 1
- 1
db/historydb/historydb_test.go

@ -293,7 +293,7 @@ func TestAccounts(t *testing.T) {
err = historyDB.AddAccounts(accs) err = historyDB.AddAccounts(accs)
assert.NoError(t, err) assert.NoError(t, err)
// Fetch accounts // Fetch accounts
fetchedAccs, err := historyDB.GetAccounts()
fetchedAccs, err := historyDB.GetAllAccounts()
assert.NoError(t, err) assert.NoError(t, err)
// Compare fetched accounts vs generated accounts // Compare fetched accounts vs generated accounts
for i, acc := range fetchedAccs { for i, acc := range fetchedAccs {

+ 10
- 1
db/statedb/statedb.go

@ -249,7 +249,6 @@ func (s *StateDB) Reset(batchNum common.BatchNum) error {
// deleted when MakeCheckpoint overwrites them. `closeCurrent` will close the // deleted when MakeCheckpoint overwrites them. `closeCurrent` will close the
// currently opened db before doing the reset. // currently opened db before doing the reset.
func (s *StateDB) reset(batchNum common.BatchNum, closeCurrent bool) error { func (s *StateDB) reset(batchNum common.BatchNum, closeCurrent bool) error {
checkpointPath := s.path + PathBatchNum + strconv.Itoa(int(batchNum))
currentPath := s.path + PathCurrent currentPath := s.path + PathCurrent
if closeCurrent { if closeCurrent {
@ -271,9 +270,19 @@ func (s *StateDB) reset(batchNum common.BatchNum, closeCurrent bool) error {
s.db = sto s.db = sto
s.idx = 255 s.idx = 255
s.currentBatch = batchNum s.currentBatch = batchNum
if s.mt != nil {
// open the MT for the current s.db
mt, err := merkletree.NewMerkleTree(s.db.WithPrefix(PrefixKeyMT), s.mt.MaxLevels())
if err != nil {
return err
}
s.mt = mt
}
return nil return nil
} }
checkpointPath := s.path + PathBatchNum + strconv.Itoa(int(batchNum))
// copy 'BatchNumX' to 'current' // copy 'BatchNumX' to 'current'
err = pebbleMakeCheckpoint(checkpointPath, currentPath) err = pebbleMakeCheckpoint(checkpointPath, currentPath)
if err != nil { if err != nil {

+ 6
- 5
synchronizer/synchronizer.go

@ -289,11 +289,12 @@ func (s *Synchronizer) reorg(uncleBlock *common.Block) (int64, error) {
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return 0, err return 0, err
} }
if batchNum != 0 {
err = s.stateDB.Reset(batchNum)
if err != nil {
return 0, err
}
if err == sql.ErrNoRows {
batchNum = 0
}
err = s.stateDB.Reset(batchNum)
if err != nil {
return 0, err
} }
return blockNum, nil return blockNum, nil

+ 165
- 65
synchronizer/synchronizer_test.go

@ -7,6 +7,7 @@ import (
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"os" "os"
"sort"
"testing" "testing"
ethCommon "github.com/ethereum/go-ethereum/common" ethCommon "github.com/ethereum/go-ethereum/common"
@ -34,6 +35,10 @@ func (t *timer) Time() int64 {
return currentTime return currentTime
} }
func accountsCmp(accounts []common.Account) func(i, j int) bool {
return func(i, j int) bool { return accounts[i].Idx < accounts[j].Idx }
}
// Check Sync output and HistoryDB state against expected values generated by // Check Sync output and HistoryDB state against expected values generated by
// til // til
func checkSyncBlock(t *testing.T, s *Synchronizer, blockNum int, block, syncBlock *common.BlockData) { func checkSyncBlock(t *testing.T, s *Synchronizer, blockNum int, block, syncBlock *common.BlockData) {
@ -194,6 +199,80 @@ func checkSyncBlock(t *testing.T, s *Synchronizer, blockNum int, block, syncBloc
assert.Equal(t, &exit, dbExit) //nolint:gosec assert.Equal(t, &exit, dbExit) //nolint:gosec
} }
} }
// Compare accounts from HistoryDB with StateDB (they should match)
dbAccounts, err := s.historyDB.GetAllAccounts()
require.Nil(t, err)
sdbAccounts, err := s.stateDB.GetAccounts()
require.Nil(t, err)
assertEqualAccountsHistoryDBStateDB(t, dbAccounts, sdbAccounts)
}
func assertEqualAccountsHistoryDBStateDB(t *testing.T, hdbAccs, sdbAccs []common.Account) {
assert.Equal(t, len(hdbAccs), len(sdbAccs))
sort.SliceStable(hdbAccs, accountsCmp(hdbAccs))
sort.SliceStable(sdbAccs, accountsCmp(sdbAccs))
for i := range hdbAccs {
hdbAcc := hdbAccs[i]
sdbAcc := sdbAccs[i]
assert.Equal(t, hdbAcc.Idx, sdbAcc.Idx)
assert.Equal(t, hdbAcc.TokenID, sdbAcc.TokenID)
assert.Equal(t, hdbAcc.EthAddr, sdbAcc.EthAddr)
assert.Equal(t, hdbAcc.PublicKey, sdbAcc.PublicKey)
}
}
// ethAddTokens adds the tokens from the blocks to the blockchain
func ethAddTokens(blocks []common.BlockData, client *test.Client) {
for _, block := range blocks {
for _, token := range block.Rollup.AddedTokens {
consts := eth.ERC20Consts{
Name: fmt.Sprintf("Token %d", token.TokenID),
Symbol: fmt.Sprintf("TK%d", token.TokenID),
Decimals: 18,
}
tokenConsts[token.TokenID] = consts
client.CtlAddERC20(token.EthAddr, consts)
}
}
}
// ethAddBlocks adds block data to the smart contracts
func ethAddBlocks(t *testing.T, blocks []common.BlockData,
client *test.Client, clientSetup *test.ClientSetup) {
for _, block := range blocks {
for _, token := range block.Rollup.AddedTokens {
_, err := client.RollupAddTokenSimple(token.EthAddr, clientSetup.RollupVariables.FeeAddToken)
require.Nil(t, err)
}
for _, tx := range block.Rollup.L1UserTxs {
client.CtlSetAddr(tx.FromEthAddr)
_, err := client.RollupL1UserTxERC20ETH(tx.FromBJJ, int64(tx.FromIdx), tx.LoadAmount, tx.Amount,
uint32(tx.TokenID), int64(tx.ToIdx))
require.Nil(t, err)
}
client.CtlSetAddr(clientSetup.AuctionVariables.BootCoordinator)
for _, batch := range block.Rollup.Batches {
_, err := client.RollupForgeBatch(&eth.RollupForgeBatchArgs{
NewLastIdx: batch.Batch.LastIdx,
NewStRoot: batch.Batch.StateRoot,
NewExitRoot: batch.Batch.ExitRoot,
L1CoordinatorTxs: batch.L1CoordinatorTxs,
L1CoordinatorTxsAuths: [][]byte{}, // Intentionally empty
L2TxsData: batch.L2Txs,
FeeIdxCoordinator: batch.Batch.FeeIdxsCoordinator,
// Circuit selector
VerifierIdx: 0, // Intentionally empty
L1Batch: batch.L1Batch,
ProofA: [2]*big.Int{}, // Intentionally empty
ProofB: [2][2]*big.Int{}, // Intentionally empty
ProofC: [2]*big.Int{}, // Intentionally empty
})
require.Nil(t, err)
}
// Mine block and sync
client.CtlMineBlock()
}
} }
func TestSync(t *testing.T) { func TestSync(t *testing.T) {
@ -322,62 +401,14 @@ func TestSync(t *testing.T) {
require.Equal(t, 3, len(blocks[i].Rollup.Batches[0].L2Txs)) require.Equal(t, 3, len(blocks[i].Rollup.Batches[0].L2Txs))
// Generate extra required data // Generate extra required data
for _, block := range blocks {
for _, token := range block.Rollup.AddedTokens {
consts := eth.ERC20Consts{
Name: fmt.Sprintf("Token %d", token.TokenID),
Symbol: fmt.Sprintf("TK%d", token.TokenID),
Decimals: 18,
}
tokenConsts[token.TokenID] = consts
client.CtlAddERC20(token.EthAddr, consts)
}
}
ethAddTokens(blocks, client)
err = tc.FillBlocksExtra(blocks, &tilCfgExtra) err = tc.FillBlocksExtra(blocks, &tilCfgExtra)
assert.Nil(t, err) assert.Nil(t, err)
tc.FillBlocksL1UserTxsBatchNum(blocks) tc.FillBlocksL1UserTxsBatchNum(blocks)
// Add block data to the smart contracts // Add block data to the smart contracts
for _, block := range blocks {
for _, token := range block.Rollup.AddedTokens {
_, err := client.RollupAddTokenSimple(token.EthAddr, clientSetup.RollupVariables.FeeAddToken)
require.Nil(t, err)
}
for _, tx := range block.Rollup.L1UserTxs {
client.CtlSetAddr(tx.FromEthAddr)
_, err := client.RollupL1UserTxERC20ETH(tx.FromBJJ, int64(tx.FromIdx), tx.LoadAmount, tx.Amount,
uint32(tx.TokenID), int64(tx.ToIdx))
require.Nil(t, err)
}
client.CtlSetAddr(bootCoordAddr)
// feeIdxCoordinator := []common.Idx{}
// if block.Block.EthBlockNum > 2 {
// // After blockNum=2 we have some accounts, use them as
// // coordinator owned to receive fees.
// feeIdxCoordinator = []common.Idx{common.Idx(256), common.Idx(259)}
// }
for _, batch := range block.Rollup.Batches {
_, err := client.RollupForgeBatch(&eth.RollupForgeBatchArgs{
NewLastIdx: batch.Batch.LastIdx,
NewStRoot: batch.Batch.StateRoot,
NewExitRoot: batch.Batch.ExitRoot,
L1CoordinatorTxs: batch.L1CoordinatorTxs,
L1CoordinatorTxsAuths: [][]byte{}, // Intentionally empty
L2TxsData: batch.L2Txs,
FeeIdxCoordinator: batch.Batch.FeeIdxsCoordinator,
// Circuit selector
VerifierIdx: 0, // Intentionally empty
L1Batch: batch.L1Batch,
ProofA: [2]*big.Int{}, // Intentionally empty
ProofB: [2][2]*big.Int{}, // Intentionally empty
ProofC: [2]*big.Int{}, // Intentionally empty
})
require.Nil(t, err)
}
// Mine block and sync
client.CtlMineBlock()
}
ethAddBlocks(t, blocks, client, clientSetup)
// //
// Sync to synchronize the current state from the test smart contracts, // Sync to synchronize the current state from the test smart contracts,
@ -467,25 +498,94 @@ func TestSync(t *testing.T) {
assert.Equal(t, auctionVars, dbAuctionVars) assert.Equal(t, auctionVars, dbAuctionVars)
assert.Equal(t, wDelayerVars, dbWDelayerVars) assert.Equal(t, wDelayerVars, dbWDelayerVars)
// TODO: Reorg will be properly tested once we have the mock ethClient implemented
/*
// Force a Reorg
lastSavedBlock, err := historyDB.GetLastBlock()
require.Nil(t, err)
//
// Reorg test
//
lastSavedBlock.EthBlockNum++
err = historyDB.AddBlock(lastSavedBlock)
require.Nil(t, err)
// Redo blocks 2-5 (as a reorg) only leaving:
// - 2 create account transactions
// - 2 add tokens
// We add a 6th block so that the synchronizer can detect the reorg
set2 := `
Type: Blockchain
lastSavedBlock.EthBlockNum++
err = historyDB.AddBlock(lastSavedBlock)
require.Nil(t, err)
AddToken(1)
AddToken(2)
CreateAccountDeposit(1) C: 2000 // Idx=256+1=257
CreateAccountCoordinator(1) A // Idx=256+0=256
> batchL1 // forge L1UserTxs{nil}, freeze defined L1UserTxs{1}
> batchL1 // forge defined L1UserTxs{1}, freeze L1UserTxs{nil}
> block // blockNum=2
> block // blockNum=3
> block // blockNum=4
> block // blockNum=5
> block // blockNum=6
`
tc = til.NewContext(common.RollupConstMaxL1UserTx)
tilCfgExtra = til.ConfigExtra{
BootCoordAddr: bootCoordAddr,
CoordUser: "A",
}
blocks, err = tc.GenerateBlocks(set2)
require.Nil(t, err)
log.Debugf("Wait for the blockchain to generate some blocks...")
time.Sleep(40 * time.Second)
for i := 0; i < 4; i++ {
client.CtlRollback()
}
blockNum := client.CtlCurrentBlock()
require.Equal(t, int64(1), blockNum)
// Generate extra required data
ethAddTokens(blocks, client)
err = tc.FillBlocksExtra(blocks, &tilCfgExtra)
assert.Nil(t, err)
tc.FillBlocksL1UserTxsBatchNum(blocks)
err = s.Sync()
// Add block data to the smart contracts
ethAddBlocks(t, blocks, client, clientSetup)
// First sync detects the reorg and discards 4 blocks
syncBlock, discards, err = s.Sync2(ctx, nil)
require.Nil(t, err)
expetedDiscards := int64(4)
require.Equal(t, &expetedDiscards, discards)
require.Nil(t, syncBlock)
// At this point, the DB only has data up to block 1
dbBlock, err := s.historyDB.GetLastBlock()
require.Nil(t, err)
assert.Equal(t, int64(1), dbBlock.EthBlockNum)
// Accounts in HistoryDB and StateDB must be empty
dbAccounts, err := s.historyDB.GetAllAccounts()
require.Nil(t, err)
sdbAccounts, err := s.stateDB.GetAccounts()
require.Nil(t, err)
assert.Equal(t, 0, len(dbAccounts))
assertEqualAccountsHistoryDBStateDB(t, dbAccounts, sdbAccounts)
// Sync blocks 2-6
for i := 0; i < 5; i++ {
syncBlock, discards, err = s.Sync2(ctx, nil)
require.Nil(t, err) require.Nil(t, err)
*/
require.Nil(t, discards)
require.NotNil(t, syncBlock)
assert.Equal(t, int64(2+i), syncBlock.Block.EthBlockNum)
}
dbBlock, err = s.historyDB.GetLastBlock()
require.Nil(t, err)
assert.Equal(t, int64(6), dbBlock.EthBlockNum)
// Accounts in HistoryDB and StateDB is only 2 entries
dbAccounts, err = s.historyDB.GetAllAccounts()
require.Nil(t, err)
sdbAccounts, err = s.stateDB.GetAccounts()
require.Nil(t, err)
assert.Equal(t, 2, len(dbAccounts))
assertEqualAccountsHistoryDBStateDB(t, dbAccounts, sdbAccounts)
} }

+ 7
- 2
test/ethclient.go

@ -67,7 +67,6 @@ type RollupBlock struct {
func (r *RollupBlock) addTransaction(tx *types.Transaction) *types.Transaction { func (r *RollupBlock) addTransaction(tx *types.Transaction) *types.Transaction {
txHash := tx.Hash() txHash := tx.Hash()
fmt.Printf("DBG txHash %v\n", txHash.Hex())
r.Txs[txHash] = tx r.Txs[txHash] = tx
return tx return tx
} }
@ -552,6 +551,13 @@ func (c *Client) CtlRollback() {
// Ethereum // Ethereum
// //
// CtlCurrentBlock returns the current blockNum without checks
func (c *Client) CtlCurrentBlock() int64 {
c.rw.RLock()
defer c.rw.RUnlock()
return c.blockNum
}
// EthCurrentBlock returns the current blockNum // EthCurrentBlock returns the current blockNum
func (c *Client) EthCurrentBlock() (int64, error) { func (c *Client) EthCurrentBlock() (int64, error) {
c.rw.RLock() c.rw.RLock()
@ -822,7 +828,6 @@ func newTransaction(name string, value interface{}) *types.Transaction {
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Printf("DBG dataJSON: %v\n", string(data))
return types.NewTransaction(0, ethCommon.Address{}, nil, 0, nil, return types.NewTransaction(0, ethCommon.Address{}, nil, 0, nil,
data) data)
} }

Loading…
Cancel
Save