github.com/aergoio/aergo@v1.3.1/chain/chainservice_test.go (about)

     1  package chain
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/aergoio/aergo/config"
     9  	"github.com/aergoio/aergo/consensus"
    10  	"github.com/aergoio/aergo/state"
    11  	"github.com/aergoio/aergo/types"
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  var (
    16  	testCfg *config.Config
    17  )
    18  
    19  const (
    20  	testPeer = "testpeer1"
    21  )
    22  
    23  type StubConsensus struct{}
    24  
    25  func (stubC *StubConsensus) SetStateDB(sdb *state.ChainStateDB) {
    26  
    27  }
    28  func (stubC *StubConsensus) IsTransactionValid(tx *types.Tx) bool {
    29  	return true
    30  }
    31  func (stubC *StubConsensus) VerifyTimestamp(block *types.Block) bool {
    32  	return true
    33  }
    34  func (stubC *StubConsensus) VerifySign(block *types.Block) error {
    35  	return nil
    36  }
    37  func (stubC *StubConsensus) IsBlockValid(block *types.Block, bestBlock *types.Block) error {
    38  	return nil
    39  }
    40  func (stubC *StubConsensus) Update(block *types.Block) {
    41  
    42  }
    43  func (stubC *StubConsensus) Save(tx consensus.TxWriter) error {
    44  	return nil
    45  }
    46  func (stubC *StubConsensus) NeedReorganization(rootNo types.BlockNo) bool {
    47  	return true
    48  }
    49  func (stubC *StubConsensus) Info() string {
    50  	return ""
    51  }
    52  func (stubC *StubConsensus) GetType() consensus.ConsensusType {
    53  	return consensus.ConsensusSBP
    54  }
    55  func (stubC *StubConsensus) NeedNotify() bool {
    56  	return true
    57  }
    58  func (stubC *StubConsensus) HasWAL() bool {
    59  	return false
    60  }
    61  func (stubC *StubConsensus) IsConnectedBlock(block *types.Block) bool {
    62  	return false
    63  }
    64  func (stubC *StubConsensus) IsForkEnable() bool {
    65  	return true
    66  }
    67  
    68  func (stubC *StubConsensus) MakeConfChangeProposal(req *types.MembershipChange) (*consensus.ConfChangePropose, error) {
    69  	return nil, consensus.ErrNotSupportedMethod
    70  }
    71  
    72  func makeBlockChain() *ChainService {
    73  	serverCtx := config.NewServerContext("", "")
    74  	testCfg = serverCtx.GetDefaultConfig().(*config.Config)
    75  	testCfg.DbType = "memorydb"
    76  	//TODO use testnet genesis for test for now
    77  	testCfg.UseTestnet = true
    78  
    79  	//TODO drop data when close memorydb when test mode
    80  	cs := NewChainService(testCfg)
    81  
    82  	stubConsensus := &StubConsensus{}
    83  
    84  	cs.SetChainConsensus(stubConsensus)
    85  
    86  	logger.Debug().Str("chainsvc name", cs.BaseComponent.GetName()).Msg("test")
    87  
    88  	return cs
    89  }
    90  
    91  func testAddBlock(t *testing.T, best int) (*ChainService, *StubBlockChain) {
    92  	cs := makeBlockChain()
    93  
    94  	genesisBlk, _ := cs.getBlockByNo(0)
    95  
    96  	assert.NotNil(t, genesisBlk)
    97  
    98  	stubChain := InitStubBlockChain([]*types.Block{genesisBlk}, best)
    99  
   100  	for i := 1; i <= best; i++ {
   101  		newBlock := stubChain.GetBlockByNo(uint64(i))
   102  		err := cs.addBlock(newBlock, nil, testPeer)
   103  		assert.NoError(t, err)
   104  
   105  		testBlockIsOnMasterChain(t, cs, newBlock)
   106  
   107  		//best block height
   108  		blk, err := cs.GetBestBlock()
   109  		assert.NoError(t, err)
   110  		assert.Equal(t, blk.BlockNo(), uint64(i))
   111  
   112  		//block hash/no mapping
   113  		var noblk *types.Block
   114  		noblk, err = cs.getBlockByNo(uint64(i))
   115  		assert.NoError(t, err)
   116  		assert.Equal(t, blk.BlockHash(), noblk.BlockHash())
   117  	}
   118  
   119  	return cs, stubChain
   120  }
   121  
   122  // Test add block to height 0 chain
   123  func testAddBlockNoTest(best int) (*ChainService, *StubBlockChain) {
   124  	cs := makeBlockChain()
   125  
   126  	genesisBlk, _ := cs.getBlockByNo(0)
   127  
   128  	stubChain := InitStubBlockChain([]*types.Block{genesisBlk}, best)
   129  
   130  	for i := 1; i <= best; i++ {
   131  		newBlock := stubChain.GetBlockByNo(uint64(i))
   132  		_ = cs.addBlock(newBlock, nil, testPeer)
   133  	}
   134  
   135  	return cs, stubChain
   136  }
   137  
   138  func TestAddBlock(t *testing.T) {
   139  	testAddBlock(t, 1)
   140  	testAddBlock(t, 10)
   141  	testAddBlock(t, 100)
   142  }
   143  
   144  // test if block exist on sideChain
   145  func testBlockIsOnMasterChain(t *testing.T, cs *ChainService, block *types.Block) {
   146  	//check if block added in DB
   147  	chainBlock, err := cs.GetBlock(block.BlockHash())
   148  	assert.NoError(t, err)
   149  	assert.Equal(t, chainBlock.GetHeader().BlockNo, block.GetHeader().BlockNo)
   150  
   151  	//check if block added on master chain
   152  	chainBlock, err = cs.getBlockByNo(block.GetHeader().BlockNo)
   153  	assert.NoError(t, err)
   154  	assert.Equal(t, chainBlock.BlockHash(), block.BlockHash())
   155  }
   156  
   157  // test if block exist on sideChain
   158  func testBlockIsOnSideChain(t *testing.T, cs *ChainService, block *types.Block) {
   159  	//check if block added in DB
   160  	chainBlock, err := cs.GetBlock(block.BlockHash())
   161  	assert.NoError(t, err)
   162  	assert.Equal(t, chainBlock.GetHeader().BlockNo, block.GetHeader().BlockNo)
   163  
   164  	//check if block added on side chain
   165  	chainBlock, err = cs.getBlockByNo(block.GetHeader().BlockNo)
   166  	assert.NoError(t, err)
   167  	assert.NotEqual(t, chainBlock.BlockHash(), block.BlockHash(), fmt.Sprintf("no=%d", block.GetHeader().BlockNo))
   168  }
   169  
   170  func testBlockIsOrphan(t *testing.T, cs *ChainService, block *types.Block) {
   171  	//check if block added in DB
   172  	_, err := cs.GetBlock(block.BlockHash())
   173  	assert.Equal(t, &ErrNoBlock{id: block.BlockHash()}, err)
   174  
   175  	//check if block exist on orphan pool
   176  	orphan := cs.op.getOrphan(block.Header.GetPrevBlockHash())
   177  	assert.Equal(t, orphan.BlockHash(), block.BlockHash())
   178  }
   179  
   180  // test to add blocks to sidechain until best of sideChain is equal to the mainChain
   181  func testSideBranch(t *testing.T, mainChainBest int) (cs *ChainService, mainChain *StubBlockChain, sideChain *StubBlockChain) {
   182  	cs, mainChain = testAddBlock(t, mainChainBest)
   183  
   184  	//common ancestor of master chain and side chain is 0
   185  	sideChain = InitStubBlockChain(mainChain.Blocks[0:1], mainChainBest)
   186  
   187  	//add sideChainBlock
   188  	for _, block := range sideChain.Blocks[1 : sideChain.Best+1] {
   189  		err := cs.addBlock(block, nil, testPeer)
   190  		assert.NoError(t, err)
   191  
   192  		//block added on sidechain
   193  		testBlockIsOnSideChain(t, cs, block)
   194  	}
   195  
   196  	assert.Equal(t, mainChain.Best, sideChain.Best)
   197  
   198  	return cs, mainChain, sideChain
   199  }
   200  
   201  func TestSideBranch(t *testing.T) {
   202  	testSideBranch(t, 5)
   203  }
   204  
   205  func TestOrphan(t *testing.T) {
   206  	mainChainBest := 5
   207  	cs, mainChain := testAddBlock(t, mainChainBest)
   208  
   209  	//make branch
   210  	sideChain := InitStubBlockChain(mainChain.Blocks[0:1], mainChainBest)
   211  
   212  	//add orphan
   213  	for _, block := range sideChain.Blocks[2 : sideChain.Best+1] {
   214  		err := cs.addBlock(block, nil, testPeer)
   215  		assert.NoError(t, err)
   216  
   217  		//block added on sidechain
   218  		testBlockIsOrphan(t, cs, block)
   219  	}
   220  }
   221  
   222  func TestSideChainReorg(t *testing.T) {
   223  	cs, mainChain, sideChain := testSideBranch(t, 5)
   224  
   225  	// add heigher block to sideChain
   226  	sideChain.GenAddBlock()
   227  	assert.Equal(t, mainChain.Best+1, sideChain.Best)
   228  
   229  	sideBestBlock, err := sideChain.GetBestBlock()
   230  	assert.NoError(t, err)
   231  
   232  	//check top block before reorg
   233  	mainBestBlock, _ := cs.GetBestBlock()
   234  	assert.Equal(t, mainChain.Best, int(mainBestBlock.GetHeader().BlockNo))
   235  	assert.Equal(t, mainChain.BestBlock.BlockHash(), mainBestBlock.BlockHash())
   236  	assert.Equal(t, mainBestBlock.GetHeader().BlockNo+1, sideBestBlock.GetHeader().BlockNo)
   237  
   238  	err = cs.addBlock(sideBestBlock, nil, testPeer)
   239  	assert.NoError(t, err)
   240  
   241  	//check if reorg is succeed
   242  	mainBestBlock, _ = cs.GetBestBlock()
   243  	assert.Equal(t, sideBestBlock.GetHeader().BlockNo, mainBestBlock.GetHeader().BlockNo)
   244  
   245  	assert.Equal(t, sideBestBlock.BlockHash(), mainBestBlock.BlockHash())
   246  }
   247  
   248  func TestAddErroredBlock(t *testing.T) {
   249  	// make chain
   250  	cs, stubChain := testAddBlock(t, 10)
   251  
   252  	// add block which occur validation error
   253  	stubChain.GenAddBlock()
   254  
   255  	newBlock, _ := stubChain.GetBestBlock()
   256  	newBlock.SetBlocksRootHash([]byte("xxx"))
   257  
   258  	err := cs.addBlock(newBlock, nil, testPeer)
   259  	assert.Equal(t, ErrorBlockVerifyStateRoot, err)
   260  
   261  	err = cs.addBlock(newBlock, nil, testPeer)
   262  	assert.Equal(t, ErrBlockCachedErrLRU, err)
   263  
   264  	cs.errBlocks.Purge()
   265  	// check error when server is rebooted
   266  	err = cs.addBlock(newBlock, nil, testPeer)
   267  	assert.Equal(t, ErrorBlockVerifyStateRoot, err)
   268  }
   269  
   270  func TestResetChain(t *testing.T) {
   271  	mainChainBest := 5
   272  	cs, mainChain := testAddBlock(t, mainChainBest)
   273  
   274  	resetHeight := uint64(3)
   275  	err := cs.cdb.ResetBest(resetHeight)
   276  	assert.NoError(t, err)
   277  
   278  	// check best
   279  	assert.Equal(t, resetHeight, cs.cdb.getBestBlockNo())
   280  
   281  	for i := uint64(mainChainBest); i > resetHeight; i-- {
   282  		err := cs.cdb.checkBlockDropped(mainChain.GetBlockByNo(i))
   283  		assert.NoError(t, err)
   284  	}
   285  }
   286  
   287  func TestReorgCrashRecoverBeforeReorgMarker(t *testing.T) {
   288  	cs, mainChain, sideChain := testSideBranch(t, 5)
   289  
   290  	// add heigher block to sideChain
   291  	sideChain.GenAddBlock()
   292  	assert.Equal(t, mainChain.Best+1, sideChain.Best)
   293  
   294  	sideBestBlock, err := sideChain.GetBestBlock()
   295  	assert.NoError(t, err)
   296  
   297  	//check top block before reorg
   298  	orgBestBlock, _ := cs.GetBestBlock()
   299  	assert.Equal(t, mainChain.Best, int(orgBestBlock.GetHeader().BlockNo))
   300  	assert.Equal(t, mainChain.BestBlock.BlockHash(), orgBestBlock.BlockHash())
   301  	assert.Equal(t, orgBestBlock.GetHeader().BlockNo+1, sideBestBlock.GetHeader().BlockNo)
   302  
   303  	TestDebugger = newDebugger()
   304  	TestDebugger.Set(DEBUG_CHAIN_STOP, 1, false)
   305  
   306  	err = cs.addBlock(sideBestBlock, nil, testPeer)
   307  	assert.Error(t, &ErrReorg{})
   308  	assert.Equal(t, err.(*ErrReorg).err, &ErrDebug{cond: DEBUG_CHAIN_STOP, value: 1})
   309  
   310  	// check if chain meta is not changed
   311  	newBestBlock, _ := cs.GetBestBlock()
   312  	assert.Equal(t, newBestBlock.GetHeader().BlockNo, orgBestBlock.GetHeader().BlockNo)
   313  
   314  	TestDebugger.clear()
   315  	cs.errBlocks.Purge()
   316  
   317  	// chain swap is not complete, so has nothing to do
   318  	err = cs.Recover()
   319  	assert.Nil(t, err)
   320  }
   321  
   322  func TestReorgCrashRecoverAfterReorgMarker(t *testing.T) {
   323  	testReorgCrashRecoverCond(t, DEBUG_CHAIN_STOP, 2)
   324  	testReorgCrashRecoverCond(t, DEBUG_CHAIN_STOP, 3)
   325  }
   326  
   327  func testReorgCrashRecoverCond(t *testing.T, cond StopCond, value int) {
   328  	cs, mainChain, sideChain := testSideBranch(t, 5)
   329  
   330  	// add heigher block to sideChain
   331  	sideChain.GenAddBlock()
   332  	assert.Equal(t, mainChain.Best+1, sideChain.Best)
   333  
   334  	sideBestBlock, err := sideChain.GetBestBlock()
   335  	assert.NoError(t, err)
   336  
   337  	//check top block before reorg
   338  	orgBestBlock, _ := cs.GetBestBlock()
   339  	assert.Equal(t, mainChain.Best, int(orgBestBlock.GetHeader().BlockNo))
   340  	assert.Equal(t, mainChain.BestBlock.BlockHash(), orgBestBlock.BlockHash())
   341  	assert.Equal(t, orgBestBlock.GetHeader().BlockNo+1, sideBestBlock.GetHeader().BlockNo)
   342  
   343  	TestDebugger = newDebugger()
   344  	TestDebugger.Set(cond, value, false)
   345  
   346  	err = cs.addBlock(sideBestBlock, nil, testPeer)
   347  	assert.Error(t, &ErrReorg{})
   348  	assert.Equal(t, err.(*ErrReorg).err, &ErrDebug{cond: cond, value: value})
   349  
   350  	assert.True(t, !checkRecoveryDone(t, cs.cdb, sideChain))
   351  
   352  	TestDebugger.clear()
   353  	cs.errBlocks.Purge()
   354  
   355  	// must recover chainDB before chainservice.Recover()
   356  	err = cs.cdb.recover()
   357  	assert.Nil(t, err)
   358  
   359  	// chain swap is not complete, so has nothing to do
   360  	err = cs.Recover()
   361  	assert.Nil(t, err)
   362  
   363  	assert.True(t, checkRecoveryDone(t, cs.cdb, sideChain))
   364  
   365  	var marker *ReorgMarker
   366  	marker, err = cs.cdb.getReorgMarker()
   367  	assert.Nil(t, err)
   368  	assert.Nil(t, marker)
   369  }
   370  
   371  // checkRecoveryDone checks if recovery is complete.
   372  // 1. all blocks of chain has (no/hash) mapping
   373  // 2. old receipts is deleted and new receipt is added if blocks have tx
   374  // 3. old tx mapping is deleted and new tx mapping is added
   375  func checkRecoveryDone(t *testing.T, cdb *ChainDB, chain *StubBlockChain) bool {
   376  	// check block mapping
   377  	for i := 0; i <= chain.Best; i++ {
   378  		block := chain.Blocks[i]
   379  		dbBlk, err := cdb.GetBlockByNo(block.GetHeader().GetBlockNo())
   380  		assert.Nil(t, err)
   381  		assert.NotNil(t, dbBlk)
   382  
   383  		if !checkBlockEqual(t, block, dbBlk) {
   384  			return false
   385  		}
   386  	}
   387  
   388  	if marker, err := cdb.getReorgMarker(); err == nil && marker != nil {
   389  		return false
   390  	}
   391  
   392  	return true
   393  }
   394  
   395  func checkBlockEqual(t *testing.T, x *types.Block, y *types.Block) bool {
   396  	if (x == nil) != (y == nil) {
   397  		t.Log("x or y is nil")
   398  		return false
   399  	}
   400  
   401  	if !bytes.Equal(x.BlockHash(), y.BlockHash()) || x.Header.GetBlockNo() != y.Header.GetBlockNo() {
   402  		t.Logf("stubchain<no=%d, %s>:db<no=%d, %s>", x.GetHeader().GetBlockNo(), x.ID(), y.GetHeader().GetBlockNo(), y.ID())
   403  		return false
   404  	}
   405  
   406  	return true
   407  }