github.com/igggame/nebulas-go@v2.1.0+incompatible/consensus/dpos/dpos_state.go (about)

     1  // Copyright (C) 2017 go-nebulas authors
     2  //
     3  // This file is part of the go-nebulas library.
     4  //
     5  // the go-nebulas library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // the go-nebulas library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU General Public License
    16  // along with the go-nebulas library.  If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  
    19  package dpos
    20  
    21  import (
    22  	"fmt"
    23  	"time"
    24  
    25  	"github.com/nebulasio/go-nebulas/consensus/pb"
    26  
    27  	"github.com/nebulasio/go-nebulas/core"
    28  	"github.com/nebulasio/go-nebulas/core/pb"
    29  	"github.com/nebulasio/go-nebulas/core/state"
    30  
    31  	"github.com/nebulasio/go-nebulas/common/trie"
    32  	"github.com/nebulasio/go-nebulas/storage"
    33  	"github.com/nebulasio/go-nebulas/util/byteutils"
    34  	"github.com/nebulasio/go-nebulas/util/logging"
    35  	"github.com/sirupsen/logrus"
    36  )
    37  
    38  // State carry context in dpos consensus
    39  type State struct {
    40  	timestamp int64
    41  	proposer  byteutils.Hash
    42  
    43  	dynastyTrie *trie.Trie // key: delegatee, val: delegatee
    44  
    45  	chain     *core.BlockChain
    46  	consensus core.Consensus
    47  }
    48  
    49  // NewState create a new dpos state
    50  func (dpos *Dpos) NewState(root *consensuspb.ConsensusRoot, stor storage.Storage, needChangeLog bool) (state.ConsensusState, error) {
    51  	var dynastyRoot byteutils.Hash
    52  	if root != nil {
    53  		dynastyRoot = root.DynastyRoot
    54  	}
    55  	dynastyTrie, err := trie.NewTrie(dynastyRoot, stor, needChangeLog)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	return &State{
    61  		timestamp: root.Timestamp,
    62  		proposer:  root.Proposer,
    63  
    64  		dynastyTrie: dynastyTrie,
    65  
    66  		chain:     dpos.chain,
    67  		consensus: dpos,
    68  	}, nil
    69  }
    70  
    71  // CheckTimeout check whether the block is timeout
    72  func (dpos *Dpos) CheckTimeout(block *core.Block) bool {
    73  	nowInMs := time.Now().Unix() * SecondInMs
    74  	blockTimeInMs := block.Timestamp() * SecondInMs
    75  	if nowInMs < blockTimeInMs {
    76  		logging.VLog().WithFields(logrus.Fields{
    77  			"block": block,
    78  			"now":   nowInMs,
    79  			"diff":  blockTimeInMs - nowInMs,
    80  			"err":   "timeout - future block",
    81  		}).Warn("Found a future block.")
    82  		return false
    83  	}
    84  	behindInMs := nowInMs - blockTimeInMs
    85  	if behindInMs > AcceptedNetWorkDelayInMs {
    86  		logging.VLog().WithFields(logrus.Fields{
    87  			"block": block,
    88  			"now":   nowInMs,
    89  			"diff":  behindInMs,
    90  			"limit": AcceptedNetWorkDelayInMs,
    91  			"err":   "timeout - expired block",
    92  		}).Warn("Found a expired block.")
    93  		return true
    94  	}
    95  	return false
    96  }
    97  
    98  // GenesisConsensusState create a new genesis dpos state
    99  func (dpos *Dpos) GenesisConsensusState(chain *core.BlockChain, conf *corepb.Genesis) (state.ConsensusState, error) {
   100  	dynastyTrie, err := trie.NewTrie(nil, chain.Storage(), false)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	if len(conf.Consensus.Dpos.Dynasty) < ConsensusSize {
   105  		return nil, ErrInitialDynastyNotEnough
   106  	}
   107  	if len(conf.Consensus.Dpos.Dynasty) != DynastySize {
   108  		return nil, ErrInvalidDynasty
   109  	}
   110  	for i := 0; i < len(conf.Consensus.Dpos.Dynasty); i++ {
   111  		addr := conf.Consensus.Dpos.Dynasty[i]
   112  		member, err := core.AddressParse(addr)
   113  		if err != nil {
   114  			return nil, err
   115  		}
   116  		v := member.Bytes()
   117  		if _, err = dynastyTrie.Put(v, v); err != nil {
   118  			return nil, err
   119  		}
   120  	}
   121  	return &State{
   122  		timestamp: core.GenesisTimestamp,
   123  		proposer:  nil,
   124  
   125  		dynastyTrie: dynastyTrie,
   126  
   127  		chain:     chain,
   128  		consensus: dpos,
   129  	}, nil
   130  }
   131  
   132  func (ds *State) String() string {
   133  	proposer := ""
   134  	if ds.proposer != nil {
   135  		proposer = ds.proposer.String()
   136  	}
   137  	return fmt.Sprintf(`{"timestamp": %d, "proposer": "%s", "dynasty": "%s"}`,
   138  		ds.timestamp,
   139  		proposer,
   140  		byteutils.Hex(ds.dynastyTrie.RootHash()),
   141  	)
   142  }
   143  
   144  // Replay a dpos
   145  func (ds *State) Replay(done state.ConsensusState) error {
   146  	state := done.(*State)
   147  	if _, err := ds.dynastyTrie.Replay(state.dynastyTrie); err != nil {
   148  		return err
   149  	}
   150  	return nil
   151  }
   152  
   153  // Clone a dpos context
   154  func (ds *State) Clone() (state.ConsensusState, error) {
   155  	var err error
   156  	dynastyTrie, err := ds.dynastyTrie.Clone()
   157  	if err != nil {
   158  		return nil, ErrCloneDynastyTrie
   159  	}
   160  	return &State{
   161  		timestamp: ds.timestamp,
   162  		proposer:  ds.proposer,
   163  
   164  		dynastyTrie: dynastyTrie,
   165  
   166  		chain:     ds.chain,
   167  		consensus: ds.consensus,
   168  	}, nil
   169  }
   170  
   171  // RootHash hash dpos state
   172  func (ds *State) RootHash() *consensuspb.ConsensusRoot {
   173  	return &consensuspb.ConsensusRoot{
   174  		DynastyRoot: ds.dynastyTrie.RootHash(),
   175  		Timestamp:   ds.TimeStamp(),
   176  		Proposer:    ds.Proposer(),
   177  	}
   178  }
   179  
   180  // Dynasty return the current dynasty
   181  func (ds *State) Dynasty() ([]byteutils.Hash, error) {
   182  	return TraverseDynasty(ds.dynastyTrie)
   183  }
   184  
   185  // DynastyRoot return the roothash of current dynasty
   186  func (ds *State) DynastyRoot() byteutils.Hash {
   187  	return ds.dynastyTrie.RootHash()
   188  }
   189  
   190  // FindProposer for now in given dynasty
   191  func FindProposer(now int64, miners []byteutils.Hash) (proposer byteutils.Hash, err error) {
   192  	nowInMs := now * SecondInMs
   193  	offsetInMs := nowInMs % DynastyIntervalInMs
   194  	if (offsetInMs % BlockIntervalInMs) != 0 {
   195  		return nil, ErrNotBlockForgTime
   196  	}
   197  	offset := offsetInMs / BlockIntervalInMs
   198  	offset %= DynastySize
   199  
   200  	if offset >= 0 && int(offset) < len(miners) {
   201  		proposer = miners[offset]
   202  	} else {
   203  		logging.VLog().WithFields(logrus.Fields{
   204  			"proposer":  proposer,
   205  			"offset":    offset,
   206  			"delegatee": len(miners),
   207  		}).Warn("Found Nil Proposer.")
   208  		return nil, ErrFoundNilProposer
   209  	}
   210  	return proposer, nil
   211  }
   212  
   213  // Proposer return the current proposer
   214  func (ds *State) Proposer() byteutils.Hash {
   215  	return ds.proposer
   216  }
   217  
   218  // TimeStamp return the current timestamp
   219  func (ds *State) TimeStamp() int64 {
   220  	return ds.timestamp
   221  }
   222  
   223  // NextConsensusState return the new state after some seconds elapsed
   224  func (ds *State) NextConsensusState(elapsedSecond int64, worldState state.WorldState) (state.ConsensusState, error) {
   225  	elapsedSecondInMs := elapsedSecond * SecondInMs
   226  	if elapsedSecondInMs <= 0 || elapsedSecondInMs%BlockIntervalInMs != 0 {
   227  		return nil, ErrNotBlockForgTime
   228  	}
   229  
   230  	dpos, ok := ds.consensus.(*Dpos)
   231  	if !ok {
   232  		logging.VLog().WithFields(logrus.Fields{
   233  			"timestamp": ds.timestamp,
   234  		}).Fatal("Type conversion failed, unexpected error.")
   235  	}
   236  	nextTimestamp := ds.timestamp + elapsedSecond
   237  	dynastyTrie, err := dpos.dynasty.getDynasty(nextTimestamp)
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	consensusState := &State{
   243  		timestamp: ds.timestamp + elapsedSecond,
   244  
   245  		dynastyTrie: dynastyTrie,
   246  
   247  		chain:     ds.chain,
   248  		consensus: ds.consensus,
   249  	}
   250  
   251  	miners, err := TraverseDynasty(dynastyTrie)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  	consensusState.proposer, err = FindProposer(consensusState.timestamp, miners)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  	return consensusState, nil
   260  }
   261  
   262  // TraverseDynasty return all members in the dynasty
   263  func TraverseDynasty(dynasty *trie.Trie) ([]byteutils.Hash, error) {
   264  	members := []byteutils.Hash{}
   265  	iter, err := dynasty.Iterator(nil)
   266  	if err != nil && err != storage.ErrKeyNotFound {
   267  		return nil, err
   268  	}
   269  	if err != nil {
   270  		return members, nil
   271  	}
   272  	exist, err := iter.Next()
   273  	for exist {
   274  		members = append(members, iter.Value())
   275  		exist, err = iter.Next()
   276  	}
   277  	return members, nil
   278  }