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 }