github.com/aergoio/aergo@v1.3.1/chain/reorg.go (about) 1 package chain 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/aergoio/aergo/consensus" 10 "github.com/aergoio/aergo/internal/enc" 11 "github.com/aergoio/aergo/message" 12 "github.com/aergoio/aergo/state" 13 "github.com/aergoio/aergo/types" 14 ) 15 16 const ( 17 initBlkCount = 20 18 ) 19 20 var ( 21 reorgKeyStr = "_reorg_marker_" 22 reorgKey = []byte(reorgKeyStr) 23 ) 24 25 var ( 26 ErrInvalidReorgMarker = errors.New("reorg marker is invalid") 27 ErrMarkerNil = errors.New("reorg marker is nil") 28 ) 29 30 type reorganizer struct { 31 //input info 32 cs *ChainService 33 bestBlock *types.Block 34 brTopBlock *types.Block //branch top block 35 36 //collected info from chain 37 brStartBlock *types.Block 38 newBlocks []*types.Block //roll forward target blocks 39 oldBlocks []*types.Block //roll back target blocks 40 41 marker *ReorgMarker 42 43 recover bool 44 45 gatherFn func() error 46 gatherPostFn func() 47 executeBlockFn func(bstate *state.BlockState, block *types.Block) error 48 } 49 50 type ErrReorgBlock struct { 51 msg string 52 53 blockNo uint64 54 blockHash []byte 55 } 56 57 func (ec *ErrReorgBlock) Error() string { 58 if ec.blockHash != nil { 59 return fmt.Sprintf("%s, block:%d,%s", ec.msg, ec.blockNo, enc.ToString(ec.blockHash)) 60 } else if ec.blockNo != 0 { 61 return fmt.Sprintf("%s, block:%d", ec.msg, ec.blockNo) 62 } else { 63 return fmt.Sprintf("%s", ec.msg) 64 } 65 } 66 67 var ( 68 ErrInvalidBranchRoot = errors.New("best block can't be branch root block") 69 ErrGatherChain = errors.New("new/old blocks must exist") 70 ErrNotExistBranchRoot = errors.New("branch root block doesn't exist") 71 ErrInvalidSwapChain = errors.New("New chain is not longer than old chain") 72 73 errMsgNoBlock = "block not found in the chain DB" 74 errMsgInvalidOldBlock = "rollback target is not valid" 75 ) 76 77 func (cs *ChainService) needReorg(block *types.Block) bool { 78 cdb := cs.cdb 79 blockNo := block.BlockNo() 80 81 latest := cdb.getBestBlockNo() 82 isNeed := latest < blockNo 83 84 if isNeed { 85 logger.Debug(). 86 Uint64("blockNo", blockNo). 87 Uint64("latestNo", latest). 88 Str("prev", block.ID()). 89 Msg("need reorganization") 90 } 91 92 return isNeed 93 } 94 95 func newReorganizer(cs *ChainService, topBlock *types.Block, marker *ReorgMarker) (*reorganizer, error) { 96 isReco := (marker != nil) 97 98 reorg := &reorganizer{ 99 cs: cs, 100 brTopBlock: topBlock, 101 newBlocks: make([]*types.Block, 0, initBlkCount), 102 oldBlocks: make([]*types.Block, 0, initBlkCount), 103 recover: isReco, 104 marker: marker, 105 } 106 107 if isReco { 108 marker.setCDB(reorg.cs.cdb) 109 110 if err := reorg.initRecovery(marker); err != nil { 111 return nil, err 112 } 113 114 reorg.gatherFn = reorg.gatherReco 115 reorg.gatherPostFn = nil 116 reorg.executeBlockFn = cs.executeBlockReco 117 } else { 118 reorg.gatherFn = reorg.gather 119 reorg.gatherPostFn = reorg.newMarker 120 reorg.executeBlockFn = cs.executeBlock 121 } 122 123 TestDebugger.Check(DEBUG_CHAIN_RANDOM_STOP, 0, nil) 124 125 return reorg, nil 126 } 127 128 //TODO: gather delete request of played tx (1 msg) 129 func (cs *ChainService) reorg(topBlock *types.Block, marker *ReorgMarker) error { 130 logger.Info().Uint64("blockNo", topBlock.GetHeader().GetBlockNo()).Str("hash", topBlock.ID()). 131 Bool("recovery", (marker != nil)).Msg("reorg started") 132 133 begT := time.Now() 134 135 reorg, err := newReorganizer(cs, topBlock, marker) 136 if err != nil { 137 logger.Error().Err(err).Msg("new reorganazier failed") 138 return err 139 } 140 141 err = reorg.gatherFn() 142 if err != nil { 143 return err 144 } 145 146 if reorg.gatherPostFn != nil { 147 reorg.gatherPostFn() 148 } 149 150 if !cs.NeedReorganization(reorg.brStartBlock.BlockNo()) { 151 return consensus.ErrorConsensus{Msg: "reorganization rejected by consensus"} 152 } 153 154 err = reorg.rollback() 155 if err != nil { 156 return err 157 } 158 159 //it's possible to occur error while executing branch block (forgery) 160 if err := reorg.rollforward(); err != nil { 161 return err 162 } 163 164 if err := reorg.swapChain(); err != nil { 165 switch ec := err.(type) { 166 case *ErrDebug: 167 return ec 168 } 169 logger.Fatal().Err(err).Msg("reorg failed while swapping chain, it can't recover") 170 return err 171 } 172 173 cs.stat.updateEvent(ReorgStat, time.Since(begT), reorg.oldBlocks[0], reorg.newBlocks[0], reorg.brStartBlock) 174 logger.Info().Msg("reorg end") 175 176 return nil 177 } 178 179 func (reorg *reorganizer) initRecovery(marker *ReorgMarker) error { 180 var startBlock, bestBlock, topBlock *types.Block 181 var err error 182 183 if marker == nil { 184 return ErrMarkerNil 185 } 186 187 topBlock = reorg.brTopBlock 188 189 cdb := reorg.cs.cdb 190 191 logger.Info().Str("marker", marker.toString()).Msg("new reorganizer") 192 193 if startBlock, err = cdb.getBlock(marker.BrStartHash); err != nil { 194 return err 195 } 196 197 if bestBlock, err = cdb.getBlock(marker.BrBestHash); err != nil { 198 return err 199 } 200 201 if bestBlock.GetHeader().GetBlockNo() >= topBlock.GetHeader().GetBlockNo() || 202 startBlock.GetHeader().GetBlockNo() >= bestBlock.GetHeader().GetBlockNo() || 203 startBlock.GetHeader().GetBlockNo() >= topBlock.GetHeader().GetBlockNo() { 204 return ErrInvalidReorgMarker 205 } 206 207 reorg.brStartBlock = startBlock 208 reorg.bestBlock = bestBlock 209 210 return nil 211 } 212 213 func (reorg *reorganizer) newMarker() { 214 if reorg.marker != nil { 215 return 216 } 217 218 reorg.marker = NewReorgMarker(reorg) 219 } 220 221 // swap oldchain to newchain oneshot (best effort) 222 // - chain height mapping 223 // - tx mapping 224 // - best block 225 func (reorg *reorganizer) swapChain() error { 226 logger.Info().Msg("swap chain to new branch") 227 228 if err := TestDebugger.Check(DEBUG_CHAIN_STOP, 1, nil); err != nil { 229 return err 230 } 231 232 if err := reorg.marker.write(); err != nil { 233 return err 234 } 235 236 if err := TestDebugger.Check(DEBUG_CHAIN_STOP, 2, nil); err != nil { 237 return err 238 } 239 240 reorg.deleteOldReceipts() 241 242 //TODO batch notification of rollforward blocks 243 244 if err := reorg.swapTxMapping(); err != nil { 245 return err 246 } 247 248 if err := reorg.swapChainMapping(); err != nil { 249 return err 250 } 251 252 if err := TestDebugger.Check(DEBUG_CHAIN_STOP, 3, nil); err != nil { 253 return err 254 } 255 256 reorg.marker.delete() 257 258 return nil 259 } 260 261 // swapChainMapping swaps chain meta from org chain to side chain and deleting reorg marker. 262 // it should be executed by 1 tx to be atomic. 263 func (reorg *reorganizer) swapChainMapping() error { 264 cdb := reorg.cs.cdb 265 266 logger.Info().Msg("swap chain mapping for new branch") 267 268 best, err := cdb.GetBestBlock() 269 if err != nil { 270 return err 271 } 272 273 if reorg.recover && bytes.Equal(best.GetHash(), reorg.brTopBlock.GetHash()) { 274 logger.Warn().Msg("swap of chain mapping has already finished") 275 return nil 276 } 277 278 if err := cdb.swapChainMapping(reorg.newBlocks); err != nil { 279 return err 280 } 281 282 return nil 283 } 284 285 func (reorg *reorganizer) swapTxMapping() error { 286 // newblock/oldblock 287 // push mempool (old - tx) 288 cs := reorg.cs 289 cdb := cs.cdb 290 291 var oldTxs = make(map[types.TxID]*types.Tx) 292 293 for _, oldBlock := range reorg.oldBlocks { 294 for _, tx := range oldBlock.GetBody().GetTxs() { 295 oldTxs[types.ToTxID(tx.GetHash())] = tx 296 } 297 } 298 299 var overwrap int 300 301 // insert new tx mapping 302 for i := len(reorg.newBlocks) - 1; i >= 0; i-- { 303 newBlock := reorg.newBlocks[i] 304 305 for _, tx := range newBlock.GetBody().GetTxs() { 306 if _, ok := oldTxs[types.ToTxID(tx.GetHash())]; ok { 307 overwrap++ 308 delete(oldTxs, types.ToTxID(tx.GetHash())) 309 } 310 } 311 312 dbTx := cs.cdb.store.NewTx() 313 314 if err := cdb.addTxsOfBlock(&dbTx, newBlock.GetBody().GetTxs(), newBlock.BlockHash()); err != nil { 315 dbTx.Discard() 316 return err 317 } 318 319 dbTx.Commit() 320 } 321 322 // delete old tx mapping 323 bulk := cdb.store.NewBulk() 324 defer bulk.DiscardLast() 325 326 for _, oldTx := range oldTxs { 327 bulk.Delete(oldTx.Hash) 328 } 329 330 bulk.Flush() 331 332 //add rollbacked Tx to mempool (except played tx in roll forward) 333 count := len(oldTxs) 334 logger.Debug().Int("tx count", count).Int("overwrapped count", overwrap).Msg("tx add to mempool") 335 336 if count > 0 { 337 //txs := make([]*types.Tx, 0, count) 338 339 for _, tx := range oldTxs { 340 // logger.Debug().Str("txID", txID.String()).Msg("tx added") 341 // txs = append(txs, tx) 342 cs.RequestTo(message.MemPoolSvc, &message.MemPoolPut{ 343 Tx: tx, 344 }) 345 } 346 // cs.RequestTo(message.MemPoolSvc, &message.MemPoolPut{ 347 // Txs: txs, 348 // }) 349 } 350 return nil 351 } 352 353 func (reorg *reorganizer) dumpOldBlocks() { 354 for _, block := range reorg.oldBlocks { 355 logger.Debug().Str("hash", block.ID()).Uint64("blockNo", block.GetHeader().GetBlockNo()). 356 Msg("dump rollback block") 357 } 358 } 359 360 // Find branch root and gather rollforard/rollback target blocks 361 func (reorg *reorganizer) gather() error { 362 //find branch root block , gather rollforward Target block 363 var err error 364 cdb := reorg.cs.cdb 365 366 bestBlock, err := cdb.GetBestBlock() 367 if err != nil { 368 return err 369 } 370 reorg.bestBlock = bestBlock 371 372 brBlock := reorg.brTopBlock 373 brBlockNo := brBlock.BlockNo() 374 375 curBestNo := cdb.getBestBlockNo() 376 377 for { 378 if brBlockNo <= curBestNo { 379 mainBlock, err := cdb.GetBlockByNo(brBlockNo) 380 // One must be able to look up any main chain block by its block 381 // no from the chain DB. 382 if err != nil { 383 return &ErrReorgBlock{errMsgNoBlock, brBlockNo, nil} 384 } 385 386 //found branch root 387 if bytes.Equal(brBlock.BlockHash(), mainBlock.BlockHash()) { 388 if curBestNo == brBlockNo { 389 return ErrInvalidBranchRoot 390 } 391 if len(reorg.newBlocks) == 0 || len(reorg.oldBlocks) == 0 { 392 return ErrGatherChain 393 } 394 reorg.brStartBlock = brBlock 395 396 logger.Debug().Str("hash", brBlock.ID()).Uint64("blockNo", brBlockNo). 397 Msg("found branch root block") 398 399 return nil 400 } 401 402 //gather rollback target 403 logger.Debug().Str("hash", mainBlock.ID()).Uint64("blockNo", brBlockNo). 404 Msg("gather rollback target") 405 reorg.oldBlocks = append(reorg.oldBlocks, mainBlock) 406 } 407 408 if brBlockNo <= 0 { 409 break 410 } 411 412 //gather rollforward target 413 logger.Debug().Str("hash", brBlock.ID()).Uint64("blockNo", brBlockNo). 414 Msg("gather rollforward target") 415 reorg.newBlocks = append(reorg.newBlocks, brBlock) 416 417 //get prev block from branch 418 if brBlock, err = cdb.getBlock(brBlock.GetHeader().GetPrevBlockHash()); err != nil { 419 return err 420 } 421 422 prevBrBlockNo := brBlock.GetHeader().GetBlockNo() 423 if brBlockNo-1 != prevBrBlockNo { 424 return &ErrReorgBlock{errMsgInvalidOldBlock, prevBrBlockNo, brBlock.BlockHash()} 425 } 426 brBlockNo = brBlock.GetHeader().GetBlockNo() 427 } 428 429 return ErrNotExistBranchRoot 430 } 431 432 // build reorg chain info from marker 433 func (reorg *reorganizer) gatherReco() error { 434 var err error 435 436 cdb := reorg.cs.cdb 437 438 startBlock := reorg.brStartBlock 439 bestBlock := reorg.bestBlock 440 topBlock := reorg.brTopBlock 441 442 reorg.brStartBlock = startBlock 443 reorg.bestBlock = bestBlock 444 445 gatherBlocksToStart := func(top *types.Block, stage string) ([]*types.Block, error) { 446 blocks := make([]*types.Block, 0) 447 448 for tmpBlk := top; tmpBlk.GetHeader().GetBlockNo() > startBlock.GetHeader().GetBlockNo(); { 449 blocks = append(blocks, tmpBlk) 450 451 logger.Debug().Str("stage", stage).Str("hash", tmpBlk.ID()).Uint64("blockNo", tmpBlk.GetHeader().GetBlockNo()). 452 Msg("gather target for reco") 453 454 if tmpBlk, err = cdb.getBlock(tmpBlk.GetHeader().GetPrevBlockHash()); err != nil { 455 return blocks, err 456 } 457 } 458 459 return blocks, nil 460 } 461 462 reorg.oldBlocks, err = gatherBlocksToStart(bestBlock, "rollback") 463 if err != nil { 464 return err 465 } 466 467 reorg.newBlocks, err = gatherBlocksToStart(topBlock, "rollforward") 468 if err != nil { 469 return err 470 } 471 472 return nil 473 } 474 475 func (reorg *reorganizer) rollback() error { 476 brStartBlock := reorg.brStartBlock 477 brStartBlockNo := brStartBlock.GetHeader().GetBlockNo() 478 479 logger.Info().Str("hash", brStartBlock.ID()).Uint64("no", brStartBlockNo).Msg("rollback chain to branch start block") 480 481 if err := reorg.cs.sdb.SetRoot(brStartBlock.GetHeader().GetBlocksRootHash()); err != nil { 482 return fmt.Errorf("failed to rollback sdb(branchRoot:no=%d,hash=%v)", brStartBlockNo, 483 brStartBlock.ID()) 484 } 485 486 reorg.cs.Update(brStartBlock) 487 488 return nil 489 } 490 491 func (reorg *reorganizer) deleteOldReceipts() { 492 dbTx := reorg.cs.cdb.NewTx() 493 for _, blk := range reorg.oldBlocks { 494 reorg.cs.cdb.deleteReceipts(&dbTx, blk.GetHash(), blk.BlockNo()) 495 } 496 dbTx.Commit() 497 } 498 499 /* 500 rollforward 501 rollforwardBlock 502 add oldTxs to mempool 503 */ 504 func (reorg *reorganizer) rollforward() error { 505 //cs := reorg.cs 506 507 logger.Info().Bool("recover", reorg.recover).Msg("rollforward chain started") 508 509 for i := len(reorg.newBlocks) - 1; i >= 0; i-- { 510 newBlock := reorg.newBlocks[i] 511 newBlockNo := newBlock.GetHeader().GetBlockNo() 512 513 if err := reorg.executeBlockFn(nil, newBlock); err != nil { 514 logger.Error().Bool("recover", reorg.recover).Str("hash", newBlock.ID()).Uint64("no", newBlockNo). 515 Msg("failed to execute block in reorg") 516 return err 517 } 518 } 519 520 return nil 521 }