github.com/codingfuture/orig-energi3@v0.8.4/energi/consensus/pos.go (about)

     1  // Copyright 2019 The Energi Core Authors
     2  // This file is part of the Energi Core library.
     3  //
     4  // The Energi Core library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The Energi Core library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the Energi Core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package consensus
    18  
    19  import (
    20  	"encoding/binary"
    21  	"errors"
    22  	"math/big"
    23  	"sort"
    24  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    27  	eth_consensus "github.com/ethereum/go-ethereum/consensus"
    28  	"github.com/ethereum/go-ethereum/core/types"
    29  	"github.com/ethereum/go-ethereum/crypto"
    30  	"github.com/ethereum/go-ethereum/log"
    31  
    32  	energi_params "energi.world/core/gen3/energi/params"
    33  )
    34  
    35  const (
    36  	MaturityPeriod    uint64 = energi_params.MaturityPeriod
    37  	AverageTimeBlocks uint64 = energi_params.AverageTimeBlocks
    38  	TargetBlockGap    uint64 = energi_params.TargetBlockGap
    39  	MinBlockGap       uint64 = energi_params.MinBlockGap
    40  	MaxFutureGap      uint64 = energi_params.MaxFutureGap
    41  	TargetPeriodGap   uint64 = energi_params.TargetPeriodGap
    42  )
    43  
    44  var (
    45  	minStake = big.NewInt(1e18)
    46  
    47  	diff1Target = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0))
    48  
    49  	errBlockMinTime = errors.New("Minimal time gap is not obeyed")
    50  
    51  	errInvalidPoSHash  = errors.New("Invalid PoS hash")
    52  	errInvalidPoSNonce = errors.New("Invalid Stake weight")
    53  )
    54  
    55  type timeTarget struct {
    56  	min_time      uint64
    57  	max_time      uint64
    58  	block_target  uint64
    59  	period_target uint64
    60  }
    61  
    62  /**
    63   * Implements block time consensus
    64   *
    65   * POS-11: Block time restrictions
    66   * POS-12: Block interval enforcement
    67   */
    68  func (e *Energi) calcTimeTarget(
    69  	chain ChainReader,
    70  	parent *types.Header,
    71  ) *timeTarget {
    72  	ret := &timeTarget{}
    73  	now := e.now()
    74  	parent_number := parent.Number.Uint64()
    75  	block_number := parent_number + 1
    76  
    77  	// POS-11: Block time restrictions
    78  	ret.max_time = now + MaxFutureGap
    79  
    80  	// POS-11: Block time restrictions
    81  	ret.min_time = parent.Time + MinBlockGap
    82  	ret.block_target = parent.Time + TargetBlockGap
    83  	ret.period_target = ret.block_target
    84  
    85  	// POS-12: Block interval enforcement
    86  	//---
    87  	if block_number >= AverageTimeBlocks {
    88  		// TODO: LRU cache here for extra DoS mitigation
    89  		past := parent
    90  
    91  		// NOTE: we have to do this way as parent may be not part of canonical
    92  		//       chain. As no mutex is held, we cannot do checks for canonical.
    93  		for i := AverageTimeBlocks - 1; i > 0; i-- {
    94  			past = chain.GetHeader(past.ParentHash, past.Number.Uint64()-1)
    95  
    96  			if past == nil {
    97  				log.Trace("Inconsistent tree, shutdown?")
    98  				return ret
    99  			}
   100  		}
   101  
   102  		ret.period_target = past.Time + TargetPeriodGap
   103  		period_min_time := ret.period_target - MinBlockGap
   104  
   105  		if period_min_time > ret.min_time {
   106  			ret.min_time = period_min_time
   107  		}
   108  	}
   109  
   110  	log.Trace("PoS time", "block", block_number,
   111  		"min", ret.min_time, "max", ret.max_time,
   112  		"block_target", ret.block_target,
   113  		"period_target", ret.period_target,
   114  	)
   115  	return ret
   116  }
   117  
   118  func (e *Energi) enforceTime(
   119  	header *types.Header,
   120  	time_target *timeTarget,
   121  ) error {
   122  	// NOTE: allow Miner to hint already tried period by
   123  	if header.Time < time_target.min_time {
   124  		header.Time = time_target.min_time
   125  	}
   126  
   127  	return nil
   128  }
   129  
   130  func (e *Energi) checkTime(
   131  	header *types.Header,
   132  	time_target *timeTarget,
   133  ) error {
   134  	if header.Time < time_target.min_time {
   135  		return errBlockMinTime
   136  	}
   137  
   138  	// Check if allowed to mine
   139  	if header.Time > time_target.max_time {
   140  		return eth_consensus.ErrFutureBlock
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  /**
   147   * Implements check modifier consensus
   148   *
   149   * POS-14: Stake modifier
   150   */
   151  func (e *Energi) calcPoSModifier(
   152  	chain ChainReader,
   153  	time uint64,
   154  	parent *types.Header,
   155  ) (ret common.Hash) {
   156  	// TODO: LRU cache here for extra DoS mitigation
   157  
   158  	// Find maturity period border
   159  	maturity_border := time
   160  
   161  	if maturity_border < MaturityPeriod {
   162  		// This should happen only in testing
   163  		maturity_border = 0
   164  	} else {
   165  		maturity_border -= MaturityPeriod
   166  	}
   167  
   168  	// Find the oldest inside maturity period
   169  	// NOTE: we have to do this walk as parent may not be part of the canonical chain
   170  	parent_height := parent.Number.Uint64()
   171  	oldest := parent
   172  
   173  	for header, num := oldest, oldest.Number.Uint64(); (header.Time > maturity_border) && (num > 0); {
   174  
   175  		oldest = header
   176  		num--
   177  		header = chain.GetHeader(header.ParentHash, num)
   178  	}
   179  
   180  	// Create Stake Modifier
   181  	ret = crypto.Keccak256Hash(
   182  		parent.Coinbase.Bytes(),
   183  		oldest.Root.Bytes(),
   184  	)
   185  
   186  	//
   187  	log.Trace("PoS modifier", "block", parent_height+1,
   188  		"modifier", ret, "oldest", oldest.Number.Uint64())
   189  	return ret
   190  }
   191  
   192  /**
   193   * Implements difficulty consensus
   194   */
   195  func (e *Energi) calcPoSDifficulty(
   196  	chain ChainReader,
   197  	time uint64,
   198  	parent *types.Header,
   199  	tt *timeTarget,
   200  ) (ret *big.Int) {
   201  	ret = e.diffFn(chain, time, parent, tt)
   202  	log.Trace("PoS difficulty", "block", parent.Number.Uint64()+1, "time", time, "diff", ret)
   203  	return ret
   204  }
   205  
   206  /**
   207   * POS-13: Difficulty algorithm (Proposal v1)
   208   */
   209  const (
   210  	diffV1_BMax     uint64 = 30
   211  	diffV1_AMax     uint64 = 120
   212  	diffV1_DivPlain uint64 = 100
   213  
   214  	// Roughly get 2x difficulty decrease
   215  	diffV1_MigrationStakerDelay  uint64 = 15
   216  	diffV1_MigrationStakerTarget uint64 = 0xFFFF
   217  )
   218  
   219  var (
   220  	diffV1_BTable []*big.Int
   221  	diffV1_ATable []*big.Int
   222  	diffV1_Div    = new(big.Int).SetUint64(diffV1_DivPlain)
   223  )
   224  
   225  func initDiffTable(l uint64, c float64) []*big.Int {
   226  	t := make([]*big.Int, l+1)
   227  	t[0] = common.Big1
   228  	var acc float64 = 1
   229  	for i := 1; i < len(t); i++ {
   230  		acc *= c
   231  		t[i] = big.NewInt(int64(acc * float64(diffV1_DivPlain)))
   232  	}
   233  	return t
   234  }
   235  func init() {
   236  	diffV1_BTable = initDiffTable(diffV1_BMax, 1.1)
   237  	diffV1_ATable = initDiffTable(diffV1_AMax, 1.05)
   238  }
   239  func calcPoSDifficultyV1(
   240  	chain ChainReader,
   241  	time uint64,
   242  	parent *types.Header,
   243  	tt *timeTarget,
   244  ) (D *big.Int) {
   245  	// Find out our target anchor
   246  	target := (tt.block_target + tt.period_target) / 2
   247  	if target < tt.min_time {
   248  		target = tt.min_time
   249  	}
   250  
   251  	if time < target {
   252  		S := target - time
   253  		if S > diffV1_BMax {
   254  			S = diffV1_BMax
   255  		}
   256  		B := diffV1_BTable[S]
   257  		D = new(big.Int).Div(
   258  			new(big.Int).Mul(parent.Difficulty, B),
   259  			diffV1_Div,
   260  		)
   261  		log.Trace("Diff multiplier", "before", S, "mult", B)
   262  	} else if time > target {
   263  		S := time - target
   264  		if S > diffV1_AMax {
   265  			S = diffV1_AMax
   266  		}
   267  		A := diffV1_ATable[S]
   268  		D = new(big.Int).Div(
   269  			new(big.Int).Mul(parent.Difficulty, diffV1_Div),
   270  			A,
   271  		)
   272  		log.Trace("Diff multiplier", "after", S, "div", A)
   273  	} else {
   274  		log.Trace("No difficulty change", "parent", parent.Difficulty)
   275  		return parent.Difficulty
   276  	}
   277  
   278  	if D.Cmp(common.Big1) < 0 {
   279  		D = common.Big1
   280  	}
   281  
   282  	log.Trace("Difficulty change",
   283  		"parent", parent.Difficulty, "new", D,
   284  		"time", time, "target", target)
   285  	return D
   286  }
   287  
   288  /**
   289   * Implements hash consensus
   290   *
   291   * POS-18: PoS hash generation
   292   * POS-22: Partial stake amount
   293   */
   294  func (e *Energi) calcPoSHash(
   295  	header *types.Header,
   296  	target *big.Int,
   297  	weight uint64,
   298  ) (poshash *big.Int, used_weight uint64) {
   299  	betime64 := [8]byte{}
   300  	binary.BigEndian.PutUint64(betime64[:], header.Time)
   301  
   302  	poshash = new(big.Int).SetBytes(crypto.Keccak256(
   303  		betime64[:],
   304  		header.MixDigest.Bytes(),
   305  		header.Coinbase.Bytes(),
   306  	))
   307  
   308  	if poshash.Cmp(target) > 0 {
   309  		mod := new(big.Int)
   310  		count, mod := new(big.Int).DivMod(poshash, target, mod)
   311  		used_weight = count.Uint64()
   312  
   313  		if mod.Cmp(common.Big0) > 0 {
   314  			used_weight += 1
   315  		}
   316  	} else {
   317  		used_weight = 1
   318  	}
   319  
   320  	if weight < used_weight {
   321  		return nil, 0
   322  	}
   323  
   324  	log.Trace("PoS hash",
   325  		"target", target,
   326  		"poshash", poshash,
   327  		"used_weight", used_weight,
   328  		"weight", weight)
   329  	return poshash, used_weight
   330  }
   331  
   332  func (e *Energi) verifyPoSHash(
   333  	chain ChainReader,
   334  	header *types.Header,
   335  ) error {
   336  	parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
   337  	weight, err := e.lookupStakeWeight(chain, header.Time, parent, header.Coinbase)
   338  	if err != nil {
   339  		return err
   340  	}
   341  
   342  	target := new(big.Int).Div(diff1Target, header.Difficulty)
   343  
   344  	poshash, used_weight := e.calcPoSHash(header, target, weight)
   345  
   346  	if poshash == nil {
   347  		return errInvalidPoSHash
   348  	}
   349  
   350  	if used_weight != header.Nonce.Uint64() {
   351  		return errInvalidPoSNonce
   352  	}
   353  
   354  	return nil
   355  }
   356  
   357  /**
   358   * Implements stake amount calculation.
   359   *
   360   * POS-3: Stake maturity period
   361   * POS-4: Stake amount
   362   * POS-22: Partial stake amount
   363   *
   364   * This is a basic helper for stake amount calculation.
   365   * There are ways to optimize it for high load, but we need something
   366   * to start with.
   367   */
   368  func (e *Energi) lookupStakeWeight(
   369  	chain ChainReader,
   370  	now uint64,
   371  	till *types.Header,
   372  	addr common.Address,
   373  ) (weight uint64, err error) {
   374  	var since uint64
   375  
   376  	if now > MaturityPeriod {
   377  		since = now - MaturityPeriod
   378  	} else {
   379  		since = 0
   380  	}
   381  
   382  	// NOTE: Do not set to high initial value due to defensive coding approach!
   383  	weight = 0
   384  	total_staked := uint64(0)
   385  	first_run := true
   386  	blockst := chain.CalculateBlockState(till.Hash(), till.Number.Uint64())
   387  
   388  	// NOTE: we need to ensure at least one iteration with the balance condition
   389  	for (till.Time > since) || first_run {
   390  		if blockst == nil {
   391  			log.Warn("PoS state root failure", "header", till.Hash())
   392  			return 0, eth_consensus.ErrMissingState
   393  		}
   394  
   395  		weight_at_block := new(big.Int).Div(
   396  			blockst.GetBalance(addr),
   397  			minStake,
   398  		).Uint64()
   399  
   400  		if first_run {
   401  			weight = weight_at_block
   402  			first_run = false
   403  		}
   404  
   405  		// Find the minimum balance
   406  		if weight > weight_at_block {
   407  			weight = weight_at_block
   408  		}
   409  
   410  		// No need to lookup further
   411  		if weight < 1 {
   412  			break
   413  		}
   414  
   415  		// POS-22: partial stake amount
   416  		if till.Coinbase == addr {
   417  			total_staked += till.Nonce.Uint64()
   418  		}
   419  
   420  		curr := till
   421  		parent_number := curr.Number.Uint64() - 1
   422  		till = chain.GetHeader(curr.ParentHash, parent_number)
   423  
   424  		if till == nil {
   425  			if curr.Number.Cmp(common.Big0) == 0 {
   426  				break
   427  			}
   428  
   429  			log.Error("PoS state missing parent", "parent", curr.ParentHash)
   430  			return 0, eth_consensus.ErrUnknownAncestor
   431  		}
   432  
   433  		blockst = chain.CalculateBlockState(curr.ParentHash, parent_number)
   434  	}
   435  
   436  	if weight < total_staked {
   437  		log.Debug("Nothing to stake",
   438  			"addr", addr, "since", since, "weight", weight, "total_staked", total_staked)
   439  		weight = 0
   440  	} else {
   441  		weight -= total_staked
   442  	}
   443  
   444  	//log.Trace("PoS stake weight", "addr", addr, "weight", weight)
   445  	return weight, nil
   446  }
   447  
   448  /**
   449   * POS-19: PoS miner implementation
   450   */
   451  func (e *Energi) mine(
   452  	chain ChainReader,
   453  	header *types.Header,
   454  	stop <-chan struct{},
   455  ) (success bool, err error) {
   456  	type Candidates struct {
   457  		addr   common.Address
   458  		weight uint64
   459  	}
   460  
   461  	accounts := e.accountsFn()
   462  	if len(accounts) == 0 {
   463  		select {
   464  		case <-stop:
   465  			return false, nil
   466  		}
   467  	}
   468  
   469  	candidates := make([]Candidates, 0, len(accounts))
   470  	migration_dpos := false
   471  	for _, a := range accounts {
   472  		candidates = append(candidates, Candidates{
   473  			addr:   a,
   474  			weight: 0,
   475  		})
   476  		//log.Trace("PoS miner candidate found", "address", a)
   477  
   478  		if a == energi_params.Energi_MigrationContract {
   479  			migration_dpos = true
   480  		}
   481  	}
   482  
   483  	//---
   484  	parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
   485  
   486  	if parent == nil {
   487  		return false, eth_consensus.ErrUnknownAncestor
   488  	}
   489  
   490  	time_target := e.calcTimeTarget(chain, parent)
   491  
   492  	blockTime := time_target.min_time
   493  
   494  	// Special case due to expected very large gap between Genesis and Migration
   495  	if header.IsGen2Migration() && !e.testing {
   496  		blockTime = e.now()
   497  	}
   498  
   499  	// A special workaround to obey target time when migration contract is used
   500  	// for mining to prevent any difficult bombs.
   501  	if migration_dpos && !e.testing {
   502  		// Obey block target
   503  		if blockTime < time_target.block_target {
   504  			blockTime = time_target.block_target
   505  		}
   506  
   507  		// Also, obey period target
   508  		if blockTime < time_target.period_target {
   509  			blockTime = time_target.period_target
   510  		}
   511  
   512  		// Decrease difficulty, if it got bumped
   513  		if header.Difficulty.Uint64() > diffV1_MigrationStakerTarget {
   514  			blockTime += diffV1_MigrationStakerDelay
   515  		}
   516  	}
   517  
   518  	//---
   519  	for ; ; blockTime++ {
   520  		if max_time := e.now() + MaxFutureGap; blockTime > max_time {
   521  			log.Trace("PoS miner is sleeping")
   522  			select {
   523  			case <-stop:
   524  				// NOTE: it's very important to ignore stop until all variants are tried
   525  				//       to prevent rogue stakers taking the initiative.
   526  				return false, nil
   527  			case <-time.After(time.Duration(blockTime-max_time) * time.Second):
   528  			}
   529  		}
   530  
   531  		if e.peerCountFn() == 0 {
   532  			log.Trace("Skipping PoS miner due to missing peers")
   533  			continue
   534  		}
   535  
   536  		header.Time = blockTime
   537  		time_target, err = e.posPrepare(chain, header, parent)
   538  		if err != nil {
   539  			return false, err
   540  		}
   541  
   542  		target := new(big.Int).Div(diff1Target, header.Difficulty)
   543  		log.Trace("PoS miner time", "time", blockTime)
   544  
   545  		// It could be done once, but then there is a chance to miss blocks.
   546  		// Some significant algo optimizations are possible, but we start with simplicity.
   547  		for i := range candidates {
   548  			v := &candidates[i]
   549  			v.weight, err = e.lookupStakeWeight(
   550  				chain, blockTime, parent, v.addr)
   551  			if err != nil {
   552  				return false, err
   553  			}
   554  		}
   555  		// Try smaller amounts first
   556  		sort.Slice(candidates, func(i, j int) bool {
   557  			return candidates[i].weight < candidates[j].weight
   558  		})
   559  		// Try to match target
   560  		for i := range candidates {
   561  			v := &candidates[i]
   562  			if v.weight < 1 {
   563  				continue
   564  			}
   565  
   566  			//log.Trace("PoS stake candidate", "addr", v.addr, "weight", v.weight)
   567  			header.Coinbase = v.addr
   568  			poshash, used_weight := e.calcPoSHash(header, target, v.weight)
   569  
   570  			nonceCap := e.GetMinerNonceCap()
   571  			if nonceCap != 0 && nonceCap < used_weight {
   572  				continue
   573  			} else if poshash != nil {
   574  				log.Trace("PoS stake", "addr", v.addr, "weight", v.weight, "used_weight", used_weight)
   575  				header.Nonce = types.EncodeNonce(used_weight)
   576  				return true, nil
   577  			}
   578  		}
   579  	}
   580  
   581  	return false, nil
   582  }