}
// SaveChainStatus save the core's newest status && delete old status
-func (s *Store) SaveChainStatus(node *state.BlockNode, view *state.UtxoViewpoint, contractView *state.ContractViewpoint, checkpoints []*state.Checkpoint, finalizedHeight uint64, finalizedHash *bc.Hash) error {
+func (s *Store) SaveChainStatus(node *state.BlockNode, view *state.UtxoViewpoint, contractView *state.ContractViewpoint, finalizedHeight uint64, finalizedHash *bc.Hash) error {
batch := s.db.NewBatch()
if err := saveUtxoView(batch, view); err != nil {
return err
return err
}
- if err := s.saveCheckpoints(batch, checkpoints); err != nil {
- return err
- }
-
bytes, err := json.Marshal(protocol.BlockStoreState{Height: node.Height, Hash: &node.Hash, FinalizedHeight: finalizedHeight, FinalizedHash: finalizedHash})
if err != nil {
return err
func (s *Store) saveCheckpoints(batch dbm.Batch, checkpoints []*state.Checkpoint) error {
for _, checkpoint := range checkpoints {
+ startTime := time.Now()
data, err := json.Marshal(checkpoint)
if err != nil {
return err
}
- if checkpoint.Height % state.BlocksOfEpoch != 1 {
+ if checkpoint.Height%state.BlocksOfEpoch != 1 {
header, err := s.GetBlockHeader(&checkpoint.Hash)
if err != nil {
return err
}
batch.Set(calcCheckpointKey(checkpoint.Height, &checkpoint.Hash), data)
+ log.WithFields(log.Fields{
+ "module": logModule,
+ "height": checkpoint.Height,
+ "hash": checkpoint.Hash.String(),
+ "status": checkpoint.Status,
+ "duration": time.Since(startTime),
+ }).Info("checkpoint saved on disk")
}
return nil
}
}
contractView := state.NewContractViewpoint()
- if err := store.SaveChainStatus(node, view, contractView, nil,0, &bc.Hash{}); err != nil {
+ if err := store.SaveChainStatus(node, view, contractView,0, &bc.Hash{}); err != nil {
t.Fatal(err)
}
func (bk *blockKeeper) checkSyncType() int {
bestHeight := bk.chain.BestBlockHeight()
- peer := bk.peers.BestIrreversiblePeer(consensus.SFFullNode | consensus.SFFastSync)
+ peer := bk.peers.BestPeer(consensus.SFFullNode | consensus.SFFastSync)
if peer != nil {
- if peerIrreversibleHeight := peer.IrreversibleHeight(); peerIrreversibleHeight >= bestHeight+minGapStartFastSync {
+ if peerJustifiedHeight := peer.JustifiedHeight(); peerJustifiedHeight >= bestHeight+minGapStartFastSync {
bk.fastSync.setSyncPeer(peer)
return fastSyncType
}
continue
}
- if err := bk.peers.BroadcastNewStatus(bk.chain.BestBlockHeader(), bk.chain.LastIrreversibleHeader()); err != nil {
+ if err := bk.peers.BroadcastNewStatus(bk.chain.BestBlockHeader(), bk.chain.LastJustifiedHeader()); err != nil {
log.WithFields(log.Fields{"module": logModule, "err": err}).Error("fail on syncWorker broadcast new status")
}
case <-bk.quit:
for _, syncPeer := range c.peers {
blockKeeper.peers.AddPeer(syncPeer.peer)
blockKeeper.peers.SetStatus(syncPeer.peer.id, syncPeer.bestHeight, nil)
- blockKeeper.peers.SetIrreversibleStatus(syncPeer.peer.id, syncPeer.irreversibleHeight, nil)
+ blockKeeper.peers.SetJustifiedStatus(syncPeer.peer.id, syncPeer.irreversibleHeight, nil)
}
gotType := blockKeeper.checkSyncType()
if c.syncType != gotType {
for _, syncPeer := range c.peers {
peers.AddPeer(syncPeer.peer)
peers.SetStatus(syncPeer.peer.id, syncPeer.bestHeight, nil)
- peers.SetIrreversibleStatus(syncPeer.peer.id, syncPeer.irreversibleHeight, nil)
+ peers.SetJustifiedStatus(syncPeer.peer.id, syncPeer.irreversibleHeight, nil)
}
mockChain := mock.NewChain()
fs := newFastSync(mockChain, &mockFetcher{baseChain: baseChain, peerStatus: peerStatus, testType: c.testType}, nil, peers)
// Chain is the interface for Bytom core
type Chain interface {
BestBlockHeader() *types.BlockHeader
- LastIrreversibleHeader() *types.BlockHeader
+ LastJustifiedHeader() *types.BlockHeader
BestBlockHeight() uint64
GetBlockByHash(*bc.Hash) (*types.Block, error)
GetBlockByHeight(uint64) (*types.Block, error)
func (m *Manager) handleStatusMsg(basePeer peers.BasePeer, msg *msgs.StatusMessage) {
if peer := m.peers.GetPeer(basePeer.ID()); peer != nil {
peer.SetBestStatus(msg.BestHeight, msg.GetBestHash())
- peer.SetIrreversibleStatus(msg.IrreversibleHeight, msg.GetIrreversibleHash())
+ peer.SetJustifiedStatus(msg.JustifiedHeight, msg.GetIrreversibleHash())
}
}
return errors.New("invalid peer")
}
- if err := p.SendStatus(m.chain.BestBlockHeader(), m.chain.LastIrreversibleHeader()); err != nil {
+ if err := p.SendStatus(m.chain.BestBlockHeader(), m.chain.LastJustifiedHeader()); err != nil {
m.peers.RemovePeer(p.ID())
return err
}
//StatusResponseMessage get status response msg
type StatusMessage struct {
- BestHeight uint64
- BestHash [32]byte
- IrreversibleHeight uint64
- IrreversibleHash [32]byte
+ BestHeight uint64
+ BestHash [32]byte
+ JustifiedHeight uint64
+ JustifiedHash [32]byte
}
//NewStatusResponseMessage construct get status response msg
-func NewStatusMessage(bestHeader, irreversibleHeader *types.BlockHeader) *StatusMessage {
+func NewStatusMessage(bestHeader, justifiedHeader *types.BlockHeader) *StatusMessage {
return &StatusMessage{
- BestHeight: bestHeader.Height,
- BestHash: bestHeader.Hash().Byte32(),
- IrreversibleHeight: irreversibleHeader.Height,
- IrreversibleHash: irreversibleHeader.Hash().Byte32(),
+ BestHeight: bestHeader.Height,
+ BestHash: bestHeader.Hash().Byte32(),
+ JustifiedHeight: justifiedHeader.Height,
+ JustifiedHash: justifiedHeader.Hash().Byte32(),
}
}
}
func (m *StatusMessage) GetIrreversibleHash() *bc.Hash {
- hash := bc.NewHash(m.IrreversibleHash)
+ hash := bc.NewHash(m.JustifiedHash)
return &hash
}
func (m *StatusMessage) String() string {
- return fmt.Sprintf("{best hash: %s, irreversible hash: %s}", hex.EncodeToString(m.BestHash[:]), hex.EncodeToString(m.IrreversibleHash[:]))
+ return fmt.Sprintf("{best hash: %s, irreversible hash: %s}", hex.EncodeToString(m.BestHash[:]), hex.EncodeToString(m.JustifiedHash[:]))
}
//TransactionMessage notify new tx msg
type Peer struct {
BasePeer
- mtx sync.RWMutex
- services consensus.ServiceFlag
- bestHeight uint64
- bestHash *bc.Hash
- irreversibleHeight uint64
- irreversibleHash *bc.Hash
- knownTxs *set.Set // Set of transaction hashes known to be known by this peer
- knownBlocks *set.Set // Set of block hashes known to be known by this peer
- knownSignatures *set.Set // Set of block signatures known to be known by this peer
- knownStatus uint64 // Set of chain status known to be known by this peer
- filterAdds *set.Set // Set of addresses that the spv node cares about.
+ mtx sync.RWMutex
+ services consensus.ServiceFlag
+ bestHeight uint64
+ bestHash *bc.Hash
+ justifiedHeight uint64
+ justifiedHash *bc.Hash
+ knownTxs *set.Set // Set of transaction hashes known to be known by this peer
+ knownBlocks *set.Set // Set of block hashes known to be known by this peer
+ knownSignatures *set.Set // Set of block signatures known to be known by this peer
+ knownStatus uint64 // Set of chain status known to be known by this peer
+ filterAdds *set.Set // Set of addresses that the spv node cares about.
}
func newPeer(basePeer BasePeer) *Peer {
return p.bestHeight
}
-func (p *Peer) IrreversibleHeight() uint64 {
+func (p *Peer) JustifiedHeight() uint64 {
p.mtx.RLock()
defer p.mtx.RUnlock()
- return p.irreversibleHeight
+ return p.justifiedHeight
}
func (p *Peer) AddFilterAddress(address []byte) {
return nil
}
-func (p *Peer) SendStatus(bestHeader, irreversibleHeader *types.BlockHeader) error {
- msg := msgs.NewStatusMessage(bestHeader, irreversibleHeader)
+func (p *Peer) SendStatus(bestHeader, justifiedHeader *types.BlockHeader) error {
+ msg := msgs.NewStatusMessage(bestHeader, justifiedHeader)
if ok := p.TrySend(msgs.BlockchainChannel, struct{ msgs.BlockchainMessage }{msg}); !ok {
return errSendStatusMsg
}
p.bestHash = bestHash
}
-func (p *Peer) SetIrreversibleStatus(irreversibleHeight uint64, irreversibleHash *bc.Hash) {
+func (p *Peer) SetJustifiedStatus(justifiedHeight uint64, justifiedHash *bc.Hash) {
p.mtx.Lock()
defer p.mtx.Unlock()
- p.irreversibleHeight = irreversibleHeight
- p.irreversibleHash = irreversibleHash
+ p.justifiedHeight = justifiedHeight
+ p.justifiedHash = justifiedHash
}
type PeerSet struct {
if !p.services.IsEnable(flag) {
continue
}
- if bestPeer == nil || p.bestHeight > bestPeer.bestHeight || (p.bestHeight == bestPeer.bestHeight && p.IsLAN()) {
- bestPeer = p
- }
- }
- return bestPeer
-}
-
-func (ps *PeerSet) BestIrreversiblePeer(flag consensus.ServiceFlag) *Peer {
- ps.mtx.RLock()
- defer ps.mtx.RUnlock()
-
- var bestPeer *Peer
- for _, p := range ps.peers {
- if !p.services.IsEnable(flag) {
- continue
- }
- if bestPeer == nil || p.irreversibleHeight > bestPeer.irreversibleHeight || (p.irreversibleHeight == bestPeer.irreversibleHeight && p.IsLAN()) {
+ if bestPeer == nil || p.JustifiedHeight() > bestPeer.JustifiedHeight() ||
+ (p.JustifiedHeight() == bestPeer.JustifiedHeight() && p.bestHeight > bestPeer.bestHeight) ||
+ (p.JustifiedHeight() == bestPeer.JustifiedHeight() && p.bestHeight == bestPeer.bestHeight && p.IsLAN()) {
bestPeer = p
}
}
return nil
}
-func (ps *PeerSet) BroadcastNewStatus(bestHeader, irreversibleHeader *types.BlockHeader) error {
- msg := msgs.NewStatusMessage(bestHeader, irreversibleHeader)
+func (ps *PeerSet) BroadcastNewStatus(bestHeader, justifiedHeader *types.BlockHeader) error {
+ msg := msgs.NewStatusMessage(bestHeader, justifiedHeader)
peers := ps.peersWithoutNewStatus(bestHeader.Height)
for _, peer := range peers {
if ok := peer.TrySend(msgs.BlockchainChannel, struct{ msgs.BlockchainMessage }{msg}); !ok {
peer.SetBestStatus(height, hash)
}
-func (ps *PeerSet) SetIrreversibleStatus(peerID string, height uint64, hash *bc.Hash) {
+func (ps *PeerSet) SetJustifiedStatus(peerID string, height uint64, hash *bc.Hash) {
peer := ps.GetPeer(peerID)
if peer == nil {
return
}
- peer.SetIrreversibleStatus(height, hash)
+ peer.SetJustifiedStatus(height, hash)
}
peer := newPeer(&basePeer{})
height := uint64(100)
hash := bc.NewHash([32]byte{0x1, 0x2})
- peer.SetIrreversibleStatus(height, &hash)
- if peer.IrreversibleHeight() != height {
+ peer.SetJustifiedStatus(height, &hash)
+ if peer.JustifiedHeight() != height {
t.Fatalf("test set Irreversible status err. got %d want %d", peer.Height(), height)
}
}
ps.AddPeer(&basePeer{id: peer2ID, serviceFlag: consensus.SFFullNode})
ps.AddPeer(&basePeer{id: peer3ID, serviceFlag: consensus.SFFastSync})
ps.AddPeer(&basePeer{id: peer4ID, serviceFlag: consensus.SFFastSync, isLan: true})
- ps.SetIrreversibleStatus(peer1ID, 1000, &block1000Hash)
- ps.SetIrreversibleStatus(peer2ID, 2000, &block2000Hash)
- ps.SetIrreversibleStatus(peer3ID, 3000, &block3000Hash)
- ps.SetIrreversibleStatus(peer4ID, 3000, &block3000Hash)
+ ps.SetJustifiedStatus(peer1ID, 1000, &block1000Hash)
+ ps.SetJustifiedStatus(peer2ID, 2000, &block2000Hash)
+ ps.SetJustifiedStatus(peer3ID, 3000, &block3000Hash)
+ ps.SetJustifiedStatus(peer4ID, 3000, &block3000Hash)
targetPeer := peer4ID
- peer := ps.BestIrreversiblePeer(consensus.SFFastSync)
+ peer := ps.BestPeer(consensus.SFFastSync)
if peer.ID() != targetPeer {
t.Fatalf("test set status err. Name of target peer %s got %s", peer4ID, peer.ID())
import (
"encoding/hex"
+ "github.com/sirupsen/logrus"
+
"github.com/bytom/bytom/config"
"github.com/bytom/bytom/errors"
"github.com/bytom/bytom/math/checked"
"github.com/bytom/bytom/protocol/state"
)
+type applyBlockReply struct {
+ verification *Verification
+ isRollback bool
+ newBestHash bc.Hash
+}
+
// ApplyBlock used to receive a new block from upper layer, it provides idempotence
// and parse the vote and mortgage from the transactions, then save to the checkpoint
// the tree of checkpoint will grow with the arrival of new blocks
// it will return verification when an epoch is reached and the current node is the validator, otherwise return nil
// the chain module must broadcast the verification
-func (c *Casper) ApplyBlock(block *types.Block) (*Verification, *state.Checkpoint, error) {
- if block.Height % state.BlocksOfEpoch == 1 {
+func (c *Casper) ApplyBlock(block *types.Block) (*applyBlockReply, error) {
+ if block.Height%state.BlocksOfEpoch == 1 {
c.newEpochCh <- block.PreviousBlockHash
}
defer c.mu.Unlock()
if _, err := c.tree.nodeByHash(block.Hash()); err == nil {
- // already processed
- return nil, nil, nil
+ return nil, errAlreadyProcessedBlock
}
+ _, oldBestHash := c.bestChain()
target, err := c.applyBlockToCheckpoint(block)
if err != nil {
- return nil, nil, errors.Wrap(err, "apply block to checkpoint")
+ return nil, errors.Wrap(err, "apply block to checkpoint")
}
- if err := c.applyTransactions(target, block.Transactions); err != nil {
- return nil, nil, err
+ if err := applyTransactions(target, block.Transactions); err != nil {
+ return nil, err
}
validators, err := c.Validators(&target.Hash)
if err != nil {
- return nil, nil, err
+ return nil, err
}
verification, err := c.applyMyVerification(target, block, validators)
if err != nil {
- return nil, nil, err
+ return nil, err
}
affectedCheckpoints, err := c.applySupLinks(target, block.SupLinks, validators)
if err != nil {
- return nil, nil, err
+ return nil, err
}
- return verification, target, c.saveCheckpoints(affectedCheckpoints, target)
+ reply := &applyBlockReply{verification: verification}
+ if c.isRollback(oldBestHash, block) {
+ reply.isRollback = true
+ reply.newBestHash = block.Hash()
+ }
+ return reply, c.saveCheckpoints(affectedCheckpoints)
}
func (c *Casper) applyBlockToCheckpoint(block *types.Block) (*state.Checkpoint, error) {
- node, err := c.tree.nodeByHash(block.PreviousBlockHash)
+ node, err := c.checkpointByHash(block.PreviousBlockHash)
if err != nil {
return nil, err
}
for pubKey, num := range parent.Guaranties {
checkpoint.Guaranties[pubKey] = num
}
- node.children = append(node.children, &treeNode{checkpoint: checkpoint})
+ node.addChild(&treeNode{checkpoint: checkpoint})
} else if mod == 0 {
checkpoint.Status = state.Unjustified
}
return checkpoint, nil
}
-func (c *Casper) applyTransactions(target *state.Checkpoint, transactions []*types.Tx) error {
+func (c *Casper) checkpointByHash(blockHash bc.Hash) (*treeNode, error) {
+ node, err := c.tree.nodeByHash(blockHash)
+ if err != nil {
+ logrus.WithField("err", err).Error("fail find checkpoint, start to reorganize checkpoint")
+
+ return c.reorganizeCheckpoint(blockHash)
+ }
+
+ return node, nil
+}
+
+func (c *Casper) isRollback(oldBestHash bc.Hash, block *types.Block) bool {
+ _, newBestHash := c.bestChain()
+ return block.Hash() == newBestHash && block.PreviousBlockHash != oldBestHash
+}
+
+func (c *Casper) reorganizeCheckpoint(hash bc.Hash) (*treeNode, error) {
+ prevHash := hash
+ var attachBlocks []*types.Block
+ for {
+ prevBlock, err := c.store.GetBlock(&prevHash)
+ if err != nil {
+ return nil, err
+ }
+
+ if prevBlock.Height%state.BlocksOfEpoch == 0 {
+ break
+ }
+
+ attachBlocks = append([]*types.Block{prevBlock}, attachBlocks...)
+ prevHash = prevBlock.PreviousBlockHash
+ }
+
+ parent, err := c.tree.nodeByHash(prevHash)
+ if err != nil {
+ return nil, err
+ }
+
+ node := &treeNode{
+ checkpoint: &state.Checkpoint{
+ ParentHash: parent.checkpoint.Hash,
+ Parent: parent.checkpoint,
+ Status: state.Growing,
+ Votes: make(map[string]uint64),
+ Guaranties: make(map[string]uint64),
+ },
+ }
+
+ parent.addChild(node)
+ for _, attachBlock := range attachBlocks {
+ if err := applyTransactions(node.checkpoint, attachBlock.Transactions); err != nil {
+ return nil, err
+ }
+
+ node.checkpoint.Hash = attachBlock.Hash()
+ node.checkpoint.Height = attachBlock.Height
+ node.checkpoint.Timestamp = attachBlock.Timestamp
+ }
+ return node, nil
+}
+
+func applyTransactions(target *state.Checkpoint, transactions []*types.Tx) error {
for _, tx := range transactions {
for _, input := range tx.Inputs {
if vetoInput, ok := input.TypedInput.(*types.VetoInput); ok {
}
// applySupLinks copy the block's supLink to the checkpoint
-func (c *Casper) applySupLinks(target *state.Checkpoint, supLinks []*types.SupLink, validators map[string]*state.Validator) (map[bc.Hash]*state.Checkpoint, error) {
+func (c *Casper) applySupLinks(target *state.Checkpoint, supLinks []*types.SupLink, validators map[string]*state.Validator) ([]*state.Checkpoint, error) {
+ affectedCheckpoints := []*state.Checkpoint{target}
if target.Height%state.BlocksOfEpoch != 0 {
return nil, nil
}
- affectedCheckpoints := make(map[bc.Hash]*state.Checkpoint)
for _, supLink := range supLinks {
var validVerifications []*Verification
for _, v := range supLinkToVerifications(supLink, validators, target.Hash, target.Height) {
return nil, err
}
- for _, c := range checkpoints {
- affectedCheckpoints[c.Hash] = c
- }
+ affectedCheckpoints = append(affectedCheckpoints, checkpoints...)
}
-
return affectedCheckpoints, nil
}
}
func (c *Casper) myVerification(target *state.Checkpoint, validators map[string]*state.Validator) (*Verification, error) {
- if target.Height % state.BlocksOfEpoch != 0 {
+ if target.Height%state.BlocksOfEpoch != 0 {
return nil, nil
}
return nil, err
}
- if err := c.verifyVerification(v, validatorOrder,false); err != nil {
+ if err := c.verifyVerification(v, validatorOrder, false); err != nil {
return nil, nil
}
return nil, nil
}
-func (c *Casper) saveCheckpoints(affectedCheckpoints map[bc.Hash]*state.Checkpoint, target *state.Checkpoint) error {
- // the target checkpoint must eventually be saved in the chain state
- delete(affectedCheckpoints, target.Hash)
-
- var checkpoints []*state.Checkpoint
- for _, c := range affectedCheckpoints {
- checkpoints = append(checkpoints, c)
- }
- return c.store.SaveCheckpoints(checkpoints)
-}
-
-
type guarantyArgs struct {
Amount uint64
PubKey []byte
return nil
}
+func (c *Casper) saveCheckpoints(checkpoints []*state.Checkpoint) error {
+ checkpointSet := make(map[bc.Hash]*state.Checkpoint)
+ for _, c := range checkpoints {
+ checkpointSet[c.Hash] = c
+ }
+
+ var result []*state.Checkpoint
+ for _, c := range checkpointSet {
+ result = append(result, c)
+ }
+
+ return c.store.SaveCheckpoints(result)
+}
+
func supLinkToVerifications(supLink *types.SupLink, validators map[string]*state.Validator, targetHash bc.Hash, targetHeight uint64) []*Verification {
validatorList := make([]*state.Validator, len(validators))
for _, validator := range validators {
return nil
}
- return c.authVerification(v, targetNode.checkpoint, validators)
+ _, oldBestHash := c.bestChain()
+ if err := c.authVerification(v, targetNode.checkpoint, validators); err != nil {
+ return err
+ }
+
+ return c.tryRollback(oldBestHash)
}
func (c *Casper) authVerification(v *Verification, target *state.Checkpoint, validators map[string]*state.Validator) error {
}
func (c *Casper) addVerificationToCheckpoint(target *state.Checkpoint, validators map[string]*state.Validator, verifications ...*Verification) ([]*state.Checkpoint, error) {
- _, oldBestHash := c.bestChain()
- var affectedCheckpoints []*state.Checkpoint
+ affectedCheckpoints := []*state.Checkpoint{target}
for _, v := range verifications {
- source, err := c.store. GetCheckpoint(&v.SourceHash)
+ source, err := c.store.GetCheckpoint(&v.SourceHash)
if err != nil {
return nil, err
}
supLink := target.AddVerification(v.SourceHash, v.SourceHeight, validators[v.PubKey].Order, v.Signature)
- affectedCheckpoints = append(affectedCheckpoints, target)
-
if target.Status != state.Unjustified || !supLink.IsMajority(len(validators)) || source.Status == state.Finalized {
continue
}
c.setJustified(source, target)
affectedCheckpoints = append(affectedCheckpoints, source)
}
-
- _, newBestHash := c.bestChain()
- if oldBestHash != newBestHash {
- c.rollbackNotifyCh <- newBestHash
- }
-
return affectedCheckpoints, nil
}
c.tree = newRoot
}
+func (c *Casper) tryRollback(oldBestHash bc.Hash) error {
+ if _, newBestHash := c.bestChain(); oldBestHash != newBestHash {
+ msg := &rollbackMsg{bestHash: newBestHash}
+ c.rollbackCh <- msg
+ return <-msg.reply
+ }
+ return nil
+}
+
func (c *Casper) authVerificationLoop() {
for blockHash := range c.newEpochCh {
validators, err := c.Validators(&blockHash)
return err
}
- verification, checkpoint, err := c.casper.ApplyBlock(block)
+ reply, err := c.casper.ApplyBlock(block)
if err != nil {
return err
}
- if verification != nil {
- if err := c.broadcastVerification(verification); err != nil {
+ if reply.verification != nil {
+ if err := c.broadcastVerification(reply.verification); err != nil {
return err
}
}
}
node := c.index.GetNode(&bcBlock.ID)
- if err := c.setState(node, utxoView, contractView, checkpoint); err != nil {
+ if err := c.setState(node, utxoView, contractView); err != nil {
return err
}
}
txsToRemove := map[bc.Hash]*types.Tx{}
- var affectedCheckpoints []*state.Checkpoint
for _, attachNode := range attachNodes {
b, err := c.store.GetBlock(&attachNode.Hash)
if err != nil {
return err
}
- verification, checkpoint, err := c.casper.ApplyBlock(b)
- if err != nil {
- return err
- }
-
- affectedCheckpoints = append(affectedCheckpoints, checkpoint)
-
- if err := c.broadcastVerification(verification); err != nil {
- return err
- }
-
if err := contractView.ApplyBlock(b); err != nil {
return err
}
log.WithFields(log.Fields{"module": logModule, "height": node.Height, "hash": node.Hash.String()}).Debug("attach from mainchain")
}
- if err := c.setState(node, utxoView, contractView, affectedCheckpoints...); err != nil {
+ if err := c.setState(node, utxoView, contractView); err != nil {
return err
}
return response.isOrphan, response.err
}
+type rollbackMsg struct {
+ bestHash bc.Hash
+ reply chan error
+}
+
func (c *Chain) blockProcessor() {
for {
select {
case msg := <-c.processBlockCh:
isOrphan, err := c.processBlock(msg.block)
msg.reply <- processBlockResponse{isOrphan: isOrphan, err: err}
- case newBestHash := <-c.rollbackNotifyCh:
- if err := c.rollback(newBestHash); err != nil {
- log.WithFields(log.Fields{"module": logModule, "err": err}).Warning("fail on rollback block")
- }
+ case msg := <-c.processRollbackCh:
+ err := c.rollback(msg.bestHash)
+ msg.reply <- err
}
}
}
log.WithFields(log.Fields{"module": logModule}).Debug("append block to the end of mainchain")
return false, c.connectBlock(bestBlock)
}
- return false, nil
+
+ log.WithFields(log.Fields{"module": logModule}).Debug("apply fork chain to casper")
+ return false, c.applyForkChainToCasper(bestNode)
+}
+
+func (c *Chain) applyForkChainToCasper(bestNode *state.BlockNode) error {
+ attachNodes, _ := c.calcReorganizeNodes(bestNode)
+ for _, node := range attachNodes {
+ block, err := c.store.GetBlock(&node.Hash)
+ if err != nil {
+ return err
+ }
+
+ reply, err := c.casper.ApplyBlock(block)
+ if err != nil {
+ return err
+ }
+
+ if reply.verification != nil {
+ if err := c.broadcastVerification(reply.verification); err != nil {
+ return err
+ }
+ }
+
+ if reply.isRollback {
+ if err := c.rollback(reply.newBestHash); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
}
-func (c *Chain) rollback(newBestHash bc.Hash) error {
- if c.bestNode.Hash == newBestHash {
+func (c *Chain) rollback(bestHash bc.Hash) error {
+ if c.bestNode.Hash == bestHash {
return nil
}
- node := c.index.GetNode(&newBestHash)
+ node := c.index.GetNode(&bestHash)
log.WithFields(log.Fields{"module": logModule}).Debug("start to reorganize chain")
return c.reorganizeChain(node)
}
errVoteToNonValidator = errors.New("pubKey of vote is not validator")
errGuarantyLessThanMinimum = errors.New("guaranty less than minimum")
errOverflow = errors.New("arithmetic overflow/underflow")
+ errAlreadyProcessedBlock = errors.New("block already processed in casper")
)
const minGuaranty = 1E14
// Casper is BFT based proof of stack consensus algorithm, it provides safety and liveness in theory,
// it's design mainly refers to https://github.com/ethereum/research/blob/master/papers/casper-basics/casper_basics.pdf
type Casper struct {
- mu sync.RWMutex
- tree *treeNode
- rollbackNotifyCh chan bc.Hash
- newEpochCh chan bc.Hash
- store Store
+ mu sync.RWMutex
+ tree *treeNode
+ rollbackCh chan *rollbackMsg
+ newEpochCh chan bc.Hash
+ store Store
// pubKey -> conflicting verifications
evilValidators map[string][]*Verification
// block hash -> previous checkpoint hash
// argument checkpoints load the checkpoints from leveldb
// the first element of checkpoints must genesis checkpoint or the last finalized checkpoint in order to reduce memory space
// the others must be successors of first one
-func NewCasper(store Store, checkpoints []*state.Checkpoint, rollbackNotifyCh chan bc.Hash) *Casper {
+func NewCasper(store Store, checkpoints []*state.Checkpoint, rollbackCh chan *rollbackMsg) *Casper {
if checkpoints[0].Height != 0 && checkpoints[0].Status != state.Finalized {
log.Panic("first element of checkpoints must genesis or in finalized status")
}
casper := &Casper{
- tree: makeTree(checkpoints[0], checkpoints[1:]),
- rollbackNotifyCh: rollbackNotifyCh,
- newEpochCh: make(chan bc.Hash),
- store: store,
- evilValidators: make(map[string][]*Verification),
- prevCheckpointCache: common.NewCache(1024),
- verificationCache: common.NewCache(1024),
+ tree: makeTree(checkpoints[0], checkpoints[1:]),
+ rollbackCh: rollbackCh,
+ newEpochCh: make(chan bc.Hash),
+ store: store,
+ evilValidators: make(map[string][]*Verification),
+ prevCheckpointCache: common.NewCache(1024),
+ verificationCache: common.NewCache(1024),
}
go casper.authVerificationLoop()
return casper
}
-// Best chain return the chain containing the justified checkpoint of the largest height
-func (c *Casper) BestChain() (uint64, bc.Hash) {
+// LastFinalized return the block height and block hash which is finalized at last
+func (c *Casper) LastFinalized() (uint64, bc.Hash) {
c.mu.RLock()
defer c.mu.RUnlock()
- return c.bestChain()
-}
-
-func (c *Casper) bestChain() (uint64, bc.Hash) {
- // root is init justified
root := c.tree.checkpoint
- bestHeight, bestHash, _ := chainOfMaxJustifiedHeight(c.tree, root.Height)
- return bestHeight, bestHash
+ return root.Height, root.Hash
}
-// LastFinalized return the block height and block hash which is finalized ast last
-func (c *Casper) LastFinalized() (uint64, bc.Hash) {
+// LastJustified return the block height and block hash which is justified at last
+func (c *Casper) LastJustified() (uint64, bc.Hash) {
c.mu.RLock()
defer c.mu.RUnlock()
- root := c.tree.checkpoint
- return root.Height, root.Hash
+ return lastJustified(c.tree)
}
// Validators return the validators by specified block hash
// e.g. if the block num of epoch is 100, and the block height corresponding to the block hash is 130, then will return the voting results of height in 0~100
func (c *Casper) Validators(blockHash *bc.Hash) (map[string]*state.Validator, error) {
- checkpoint, err := c.prevCheckpoint(blockHash)
+ checkpoint, err := c.parentCheckpoint(blockHash)
if err != nil {
return nil, err
}
return checkpoint.Validators(), nil
}
-func (c *Casper) prevCheckpoint(blockHash *bc.Hash) (*state.Checkpoint, error) {
+func (c *Casper) parentCheckpoint(blockHash *bc.Hash) (*state.Checkpoint, error) {
hash, err := c.prevCheckpointHash(blockHash)
if err != nil {
return nil, err
return c.store.GetCheckpoint(hash)
}
-func (c *Casper) prevCheckpointByPrevHash(prevBlockHash *bc.Hash) (*state.Checkpoint, error) {
+func (c *Casper) parentCheckpointByPrevHash(prevBlockHash *bc.Hash) (*state.Checkpoint, error) {
hash, err := c.prevCheckpointHashByPrevHash(prevBlockHash)
if err != nil {
return nil, err
return validators
}
+func (c *Casper) bestChain() (uint64, bc.Hash) {
+ // root is init justified
+ root := c.tree.checkpoint
+ bestHeight, bestHash, _ := chainOfMaxJustifiedHeight(c.tree, root.Height)
+ return bestHeight, bestHash
+}
+
+func lastJustified(node *treeNode) (uint64, bc.Hash) {
+ lastJustifiedHeight, lastJustifiedHash := uint64(0), bc.Hash{}
+ if node.checkpoint.Status == state.Justified {
+ lastJustifiedHeight, lastJustifiedHash = node.checkpoint.Height, node.checkpoint.Hash
+ }
+
+ for _, child := range node.children {
+ if justifiedHeight, justifiedHash := lastJustified(child); justifiedHeight > lastJustifiedHeight {
+ lastJustifiedHeight, lastJustifiedHash = justifiedHeight, justifiedHash
+ }
+ }
+ return lastJustifiedHeight, lastJustifiedHash
+}
+
// justifiedHeight is the max justified height of checkpoint from node to root
func chainOfMaxJustifiedHeight(node *treeNode, justifiedHeight uint64) (uint64, bc.Hash, uint64) {
checkpoint := node.checkpoint
bestHeight, bestHash, maxJustifiedHeight := checkpoint.Height, checkpoint.Hash, justifiedHeight
for _, child := range node.children {
- if height, hash, justified := chainOfMaxJustifiedHeight(child, justifiedHeight); justified >= maxJustifiedHeight {
+ if height, hash, justified := chainOfMaxJustifiedHeight(child, justifiedHeight); justified > maxJustifiedHeight || height > bestHeight {
bestHeight, bestHash, maxJustifiedHeight = height, hash, justified
}
}
"github.com/bytom/bytom/protocol/state"
)
-const maxProcessBlockChSize = 1024
+const (
+ maxProcessBlockChSize = 1024
+ maxProcessRollbackSize = 1024
+)
// Chain provides functions for working with the Bytom block chain.
type Chain struct {
- index *state.BlockIndex
- orphanManage *OrphanManage
- txPool *TxPool
- store Store
- casper *Casper
- processBlockCh chan *processBlockMsg
- rollbackNotifyCh chan bc.Hash
- eventDispatcher *event.Dispatcher
+ index *state.BlockIndex
+ orphanManage *OrphanManage
+ txPool *TxPool
+ store Store
+ casper *Casper
+ processBlockCh chan *processBlockMsg
+ processRollbackCh chan *rollbackMsg
+ eventDispatcher *event.Dispatcher
cond sync.Cond
bestNode *state.BlockNode
func NewChainWithOrphanManage(store Store, txPool *TxPool, manage *OrphanManage, eventDispatcher *event.Dispatcher) (*Chain, error) {
c := &Chain{
- orphanManage: manage,
- eventDispatcher: eventDispatcher,
- txPool: txPool,
- store: store,
- rollbackNotifyCh: make(chan bc.Hash),
- processBlockCh: make(chan *processBlockMsg, maxProcessBlockChSize),
+ orphanManage: manage,
+ eventDispatcher: eventDispatcher,
+ txPool: txPool,
+ store: store,
+ processRollbackCh: make(chan *rollbackMsg, maxProcessRollbackSize),
+ processBlockCh: make(chan *processBlockMsg, maxProcessBlockChSize),
}
c.cond.L = new(sync.Mutex)
c.bestNode = c.index.GetNode(storeStatus.Hash)
c.index.SetMainChain(c.bestNode)
- casper, err := newCasper(store, storeStatus, c.rollbackNotifyCh)
+ casper, err := newCasper(store, storeStatus, c.processRollbackCh)
if err != nil {
return nil, err
}
Status: state.Justified,
}
+ if err := c.store.SaveCheckpoints([]*state.Checkpoint{checkpoint}); err != nil {
+ return err
+ }
+
utxoView := state.NewUtxoViewpoint()
bcBlock := types.MapBlock(genesisBlock)
if err := utxoView.ApplyBlock(bcBlock); err != nil {
}
contractView := state.NewContractViewpoint()
- return c.store.SaveChainStatus(node, utxoView, contractView, []*state.Checkpoint{checkpoint}, 0, &checkpoint.Hash)
+ return c.store.SaveChainStatus(node, utxoView, contractView, 0, &checkpoint.Hash)
}
-func newCasper(store Store, storeStatus *BlockStoreState, rollbackNotifyCh chan bc.Hash) (*Casper, error) {
+func newCasper(store Store, storeStatus *BlockStoreState, rollbackCh chan *rollbackMsg) (*Casper, error) {
checkpoints, err := store.CheckpointsFromNode(storeStatus.FinalizedHeight, storeStatus.FinalizedHash)
if err != nil {
return nil, err
}
- return NewCasper(store, checkpoints, rollbackNotifyCh), nil
+ return NewCasper(store, checkpoints, rollbackCh), nil
}
-// BestBlockHeight returns the last irreversible block header of the blockchain
-func (c *Chain) LastIrreversibleHeader() *types.BlockHeader {
- _, hash := c.casper.LastFinalized()
+// LastFinalizedHeader returns the last finalized block header of the block chain
+func (c *Chain) LastJustifiedHeader() *types.BlockHeader {
+ _, hash := c.casper.LastJustified()
node := c.index.GetNode(&hash)
return node.BlockHeader()
}
// GetValidator return validator by specified blockHash and timestamp
func (c *Chain) GetValidator(prevHash *bc.Hash, timeStamp uint64) (*state.Validator, error) {
- prevCheckpoint, err := c.casper.prevCheckpointByPrevHash(prevHash)
+ prevCheckpoint, err := c.casper.parentCheckpointByPrevHash(prevHash)
if err != nil {
return nil, err
}
}
// This function must be called with mu lock in above level
-func (c *Chain) setState(node *state.BlockNode, view *state.UtxoViewpoint, contractView *state.ContractViewpoint, checkpoints ...*state.Checkpoint) error {
+func (c *Chain) setState(node *state.BlockNode, view *state.UtxoViewpoint, contractView *state.ContractViewpoint) error {
finalizedHeight, finalizedHash := c.casper.LastFinalized()
- if err := c.store.SaveChainStatus(node, view, contractView, checkpoints, finalizedHeight, &finalizedHash); err != nil {
+ if err := c.store.SaveChainStatus(node, view, contractView, finalizedHeight, &finalizedHash); err != nil {
return err
}
LoadBlockIndex(uint64) (*state.BlockIndex, error)
SaveBlock(*types.Block) error
SaveBlockHeader(*types.BlockHeader) error
- SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint, *state.ContractViewpoint, []*state.Checkpoint, uint64, *bc.Hash) error
+ SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint, *state.ContractViewpoint, uint64, *bc.Hash) error
}
// BlockStoreState represents the core's db status
return rootNode
}
+func (t *treeNode) addChild(child *treeNode) {
+ for i, n := range t.children {
+ if n.checkpoint.Hash == child.checkpoint.Hash {
+ t.children[i] = child
+ return
+ }
+ }
+ t.children = append(t.children, child)
+}
+
func (t *treeNode) nodeByHash(blockHash bc.Hash) (*treeNode, error) {
if c := t.findOnlyOne(func(c *state.Checkpoint) bool {
return c.Hash == blockHash
func (s *mockStore) LoadBlockIndex(uint64) (*state.BlockIndex, error) { return nil, nil }
func (s *mockStore) SaveBlock(*types.Block) error { return nil }
func (s *mockStore) SaveBlockHeader(*types.BlockHeader) error { return nil }
-func (s *mockStore) SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint, *state.ContractViewpoint, []*state.Checkpoint, uint64, *bc.Hash) error {
+func (s *mockStore) SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint, *state.ContractViewpoint, uint64, *bc.Hash) error {
return nil
}
func (s *mockStore1) LoadBlockIndex(uint64) (*state.BlockIndex, error) { return nil, nil }
func (s *mockStore1) SaveBlock(*types.Block) error { return nil }
func (s *mockStore1) SaveBlockHeader(*types.BlockHeader) error { return nil }
-func (s *mockStore1) SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint, *state.ContractViewpoint, []*state.Checkpoint, uint64, *bc.Hash) error { return nil}
+func (s *mockStore1) SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint, *state.ContractViewpoint, uint64, *bc.Hash) error { return nil}
func TestProcessTransaction(t *testing.T) {
txPool := &TxPool{
}
}
-func (c *Chain) LastIrreversibleHeader() *types.BlockHeader {
+func (c *Chain) LastJustifiedHeader() *types.BlockHeader {
return nil
}
utxoViewpoint0.Entries[k] = v
}
contractView := state.NewContractViewpoint()
- if err := store.SaveChainStatus(node, utxoViewpoint0, contractView, nil, 0, &bc.Hash{}); err != nil {
+ if err := store.SaveChainStatus(node, utxoViewpoint0, contractView, 0, &bc.Hash{}); err != nil {
t.Error(err)
}
t.Error(err)
}
}
- if err := store.SaveChainStatus(node, utxoViewpoint, contractView, nil,0, &bc.Hash{}); err != nil {
+ if err := store.SaveChainStatus(node, utxoViewpoint, contractView,0, &bc.Hash{}); err != nil {
t.Error(err)
}