github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/poll/staking_committee.go (about)

     1  // Copyright (c) 2020 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package poll
     7  
     8  import (
     9  	"context"
    10  	"encoding/hex"
    11  	"math/big"
    12  	"time"
    13  
    14  	"github.com/ethereum/go-ethereum/common"
    15  	"github.com/ethereum/go-ethereum/common/hexutil"
    16  	"github.com/ethereum/go-ethereum/crypto"
    17  	"github.com/iotexproject/go-pkgs/hash"
    18  	"github.com/iotexproject/iotex-address/address"
    19  	"github.com/iotexproject/iotex-election/committee"
    20  	"github.com/iotexproject/iotex-election/types"
    21  	"github.com/iotexproject/iotex-election/util"
    22  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    23  	"github.com/pkg/errors"
    24  	"go.uber.org/zap"
    25  
    26  	"github.com/iotexproject/iotex-core/action"
    27  	"github.com/iotexproject/iotex-core/action/protocol"
    28  	"github.com/iotexproject/iotex-core/action/protocol/execution/evm"
    29  	"github.com/iotexproject/iotex-core/action/protocol/rolldpos"
    30  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    31  	"github.com/iotexproject/iotex-core/pkg/log"
    32  	"github.com/iotexproject/iotex-core/pkg/prometheustimer"
    33  	"github.com/iotexproject/iotex-core/state"
    34  )
    35  
    36  var (
    37  	_nativeStakingContractCreator = address.ZeroAddress
    38  	_nativeStakingContractNonce   = uint64(0)
    39  	// this is a special execution that is not signed, set hash = hex-string of "_nativeStakingContractHash"
    40  	_nativeStakingContractHash, _ = hash.HexStringToHash256("000000000000006e61746976655374616b696e67436f6e747261637448617368")
    41  )
    42  
    43  type stakingCommittee struct {
    44  	electionCommittee    committee.Committee
    45  	governanceStaking    Protocol
    46  	nativeStaking        *NativeStaking
    47  	scoreThreshold       *big.Int
    48  	currentNativeBuckets []*types.Bucket
    49  	timerFactory         *prometheustimer.TimerFactory
    50  }
    51  
    52  // NewStakingCommittee creates a staking committee which fetch result from governance chain and native staking
    53  func NewStakingCommittee(
    54  	ec committee.Committee,
    55  	gs Protocol,
    56  	readContract ReadContract,
    57  	nativeStakingContractAddress string,
    58  	nativeStakingContractCode string,
    59  	scoreThreshold *big.Int,
    60  ) (Protocol, error) {
    61  	var ns *NativeStaking
    62  	if nativeStakingContractAddress != "" || nativeStakingContractCode != "" {
    63  		var err error
    64  		if ns, err = NewNativeStaking(readContract); err != nil {
    65  			return nil, errors.New("failed to create native staking")
    66  		}
    67  		if nativeStakingContractAddress != "" {
    68  			ns.SetContract(nativeStakingContractAddress)
    69  		}
    70  	}
    71  
    72  	timerFactory, err := prometheustimer.New(
    73  		"iotex_staking_perf",
    74  		"Performance of staking module",
    75  		[]string{"type"},
    76  		[]string{"default"},
    77  	)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	sc := stakingCommittee{
    83  		electionCommittee: ec,
    84  		governanceStaking: gs,
    85  		nativeStaking:     ns,
    86  		scoreThreshold:    scoreThreshold,
    87  	}
    88  	sc.timerFactory = timerFactory
    89  
    90  	return &sc, nil
    91  }
    92  
    93  func (sc *stakingCommittee) CreateGenesisStates(ctx context.Context, sm protocol.StateManager) error {
    94  	if gsc, ok := sc.governanceStaking.(protocol.GenesisStateCreator); ok {
    95  		if err := gsc.CreateGenesisStates(ctx, sm); err != nil {
    96  			return err
    97  		}
    98  	}
    99  	g := genesis.MustExtractGenesisContext(ctx)
   100  	blkCtx := protocol.MustGetBlockCtx(ctx)
   101  	if blkCtx.BlockHeight != 0 {
   102  		return errors.Errorf("Cannot create genesis state for height %d", blkCtx.BlockHeight)
   103  	}
   104  	if g.NativeStakingContractCode == "" || g.NativeStakingContractAddress != "" {
   105  		return nil
   106  	}
   107  	blkCtx.Producer, _ = address.FromString(address.ZeroAddress)
   108  	blkCtx.GasLimit = g.BlockGasLimitByHeight(0)
   109  	bytes, err := hexutil.Decode(g.NativeStakingContractCode)
   110  	if err != nil {
   111  		return err
   112  	}
   113  	execution, err := action.NewExecution(
   114  		"",
   115  		_nativeStakingContractNonce,
   116  		big.NewInt(0),
   117  		g.BlockGasLimitByHeight(0),
   118  		big.NewInt(0),
   119  		bytes,
   120  	)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	actionCtx := protocol.ActionCtx{}
   125  	actionCtx.Caller, err = address.FromString(_nativeStakingContractCreator)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	actionCtx.Nonce = _nativeStakingContractNonce
   130  	actionCtx.ActionHash = _nativeStakingContractHash
   131  	actionCtx.GasPrice = execution.GasPrice()
   132  	actionCtx.IntrinsicGas, err = execution.IntrinsicGas()
   133  	if err != nil {
   134  		return err
   135  	}
   136  	ctx = protocol.WithActionCtx(ctx, actionCtx)
   137  	ctx = protocol.WithBlockCtx(ctx, blkCtx)
   138  	ctx = evm.WithHelperCtx(ctx, evm.HelperContext{
   139  		GetBlockHash: func(height uint64) (hash.Hash256, error) {
   140  			return hash.ZeroHash256, nil
   141  		},
   142  		GetBlockTime: func(u uint64) (time.Time, error) {
   143  			// make sure the returned timestamp is after the current block time so that evm upgrades based on timestamp (Shanghai and onwards) are disabled
   144  			return blkCtx.BlockTimeStamp.Add(5 * time.Second), nil
   145  		},
   146  		DepositGasFunc: func(context.Context, protocol.StateManager, address.Address, *big.Int, *big.Int) (*action.TransactionLog, error) {
   147  			return nil, nil
   148  		},
   149  		Sgd: nil,
   150  	})
   151  	// deploy native staking contract
   152  	_, receipt, err := evm.ExecuteContract(
   153  		ctx,
   154  		sm,
   155  		execution,
   156  	)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) {
   161  		return errors.Errorf("error when deploying native staking contract, status=%d", receipt.Status)
   162  	}
   163  	sc.SetNativeStakingContract(receipt.ContractAddress)
   164  	log.L().Info("Deployed native staking contract", zap.String("address", receipt.ContractAddress))
   165  
   166  	return nil
   167  }
   168  
   169  func (sc *stakingCommittee) Start(ctx context.Context, sr protocol.StateReader) (interface{}, error) {
   170  	g := genesis.MustExtractGenesisContext(ctx)
   171  	if g.NativeStakingContractAddress == "" && g.NativeStakingContractCode != "" {
   172  		caller, _ := address.FromString(_nativeStakingContractCreator)
   173  		ethAddr := crypto.CreateAddress(common.BytesToAddress(caller.Bytes()), _nativeStakingContractNonce)
   174  		iotxAddr, _ := address.FromBytes(ethAddr.Bytes())
   175  		sc.SetNativeStakingContract(iotxAddr.String())
   176  		log.L().Info("Loaded native staking contract", zap.String("address", iotxAddr.String()))
   177  	}
   178  
   179  	return nil, nil
   180  }
   181  
   182  func (sc *stakingCommittee) CreatePreStates(ctx context.Context, sm protocol.StateManager) error {
   183  	if psc, ok := sc.governanceStaking.(protocol.PreStatesCreator); ok {
   184  		return psc.CreatePreStates(ctx, sm)
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  func (sc *stakingCommittee) CreatePostSystemActions(ctx context.Context, sr protocol.StateReader) ([]action.Envelope, error) {
   191  	return createPostSystemActions(ctx, sr, sc)
   192  }
   193  
   194  func (sc *stakingCommittee) Handle(ctx context.Context, act action.Action, sm protocol.StateManager) (*action.Receipt, error) {
   195  	receipt, err := sc.governanceStaking.Handle(ctx, act, sm)
   196  	if err := sc.persistNativeBuckets(ctx, receipt, err); err != nil {
   197  		return nil, err
   198  	}
   199  	return receipt, err
   200  }
   201  
   202  func (sc *stakingCommittee) Validate(ctx context.Context, act action.Action, sr protocol.StateReader) error {
   203  	return validate(ctx, sr, sc, act)
   204  }
   205  
   206  func (sc *stakingCommittee) Name() string {
   207  	return _protocolID
   208  }
   209  
   210  // CalculateCandidatesByHeight calculates delegates with native staking and returns merged list
   211  func (sc *stakingCommittee) CalculateCandidatesByHeight(ctx context.Context, sr protocol.StateReader, height uint64) (state.CandidateList, error) {
   212  	timer := sc.timerFactory.NewTimer("Governance")
   213  	cand, err := sc.governanceStaking.CalculateCandidatesByHeight(ctx, sr, height)
   214  	timer.End()
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  
   219  	bcCtx := protocol.MustGetBlockchainCtx(ctx)
   220  	featureCtx := protocol.MustGetFeatureWithHeightCtx(ctx)
   221  	rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx))
   222  	// convert to epoch start height
   223  	if !featureCtx.EnableNativeStaking(rp.GetEpochHeight(rp.GetEpochNum(height))) {
   224  		return sc.filterCandidates(cand), nil
   225  	}
   226  	// native staking using contract starts from Cook
   227  	if sc.nativeStaking == nil {
   228  		return nil, errors.New("native staking was not set after cook height")
   229  	}
   230  
   231  	// TODO: extract tip info inside of Votes function
   232  	timer = sc.timerFactory.NewTimer("Native")
   233  	nativeVotes, err := sc.nativeStaking.Votes(ctx, bcCtx.Tip.Timestamp, featureCtx.StakingCorrectGas(height))
   234  	timer.End()
   235  	if err == ErrNoData {
   236  		// no native staking data
   237  		return sc.filterCandidates(cand), nil
   238  	}
   239  	if err != nil {
   240  		return nil, errors.Wrap(err, "failed to get native chain candidates")
   241  	}
   242  	sc.currentNativeBuckets = nativeVotes.Buckets
   243  
   244  	return sc.mergeCandidates(cand, nativeVotes, bcCtx.Tip.Timestamp), nil
   245  }
   246  
   247  func (sc *stakingCommittee) CalculateUnproductiveDelegates(
   248  	ctx context.Context,
   249  	sr protocol.StateReader,
   250  ) ([]string, error) {
   251  	return sc.governanceStaking.CalculateUnproductiveDelegates(ctx, sr)
   252  }
   253  
   254  func (sc *stakingCommittee) Delegates(ctx context.Context, sr protocol.StateReader) (state.CandidateList, error) {
   255  	return sc.governanceStaking.Delegates(ctx, sr)
   256  }
   257  
   258  func (sc *stakingCommittee) NextDelegates(ctx context.Context, sr protocol.StateReader) (state.CandidateList, error) {
   259  	return sc.governanceStaking.NextDelegates(ctx, sr)
   260  }
   261  
   262  func (sc *stakingCommittee) Candidates(ctx context.Context, sr protocol.StateReader) (state.CandidateList, error) {
   263  	return sc.governanceStaking.Candidates(ctx, sr)
   264  }
   265  
   266  func (sc *stakingCommittee) NextCandidates(ctx context.Context, sr protocol.StateReader) (state.CandidateList, error) {
   267  	return sc.governanceStaking.NextCandidates(ctx, sr)
   268  }
   269  
   270  func (sc *stakingCommittee) ReadState(ctx context.Context, sr protocol.StateReader, method []byte, args ...[]byte) ([]byte, uint64, error) {
   271  	return sc.governanceStaking.ReadState(ctx, sr, method, args...)
   272  }
   273  
   274  // Register registers the protocol with a unique ID
   275  func (sc *stakingCommittee) Register(r *protocol.Registry) error {
   276  	return r.Register(_protocolID, sc)
   277  }
   278  
   279  // ForceRegister registers the protocol with a unique ID and force replacing the previous protocol if it exists
   280  func (sc *stakingCommittee) ForceRegister(r *protocol.Registry) error {
   281  	return r.ForceRegister(_protocolID, sc)
   282  }
   283  
   284  // SetNativeStakingContract sets the address of native staking contract
   285  func (sc *stakingCommittee) SetNativeStakingContract(contract string) {
   286  	sc.nativeStaking.SetContract(contract)
   287  }
   288  
   289  // return candidates whose votes are above threshold
   290  func (sc *stakingCommittee) filterCandidates(candidates state.CandidateList) state.CandidateList {
   291  	var cand state.CandidateList
   292  	for _, c := range candidates {
   293  		if c.Votes.Cmp(sc.scoreThreshold) >= 0 {
   294  			cand = append(cand, c)
   295  		}
   296  	}
   297  	return cand
   298  }
   299  
   300  func (sc *stakingCommittee) mergeCandidates(list state.CandidateList, votes *VoteTally, ts time.Time) state.CandidateList {
   301  	// as of now, native staking does not have register contract, only voting/staking contract
   302  	// it is assumed that all votes done on native staking target for delegates registered on Ethereum
   303  	// votes cast to all outside address will not be counted and simply ignored
   304  	candidates := make(map[string]*state.Candidate)
   305  	candidateScores := make(map[string]*big.Int)
   306  	for _, cand := range list {
   307  		clone := cand.Clone()
   308  		name := to12Bytes(clone.CanName)
   309  		if v, ok := votes.Candidates[name]; ok {
   310  			clone.Votes.Add(clone.Votes, v.Votes)
   311  		}
   312  		if clone.Votes.Cmp(sc.scoreThreshold) >= 0 {
   313  			candidates[hex.EncodeToString(name[:])] = clone
   314  			candidateScores[hex.EncodeToString(name[:])] = clone.Votes
   315  		}
   316  	}
   317  	sorted := util.Sort(candidateScores, uint64(ts.Unix()))
   318  	var merged state.CandidateList
   319  	for _, name := range sorted {
   320  		merged = append(merged, candidates[name])
   321  	}
   322  	return merged
   323  }
   324  
   325  func (sc *stakingCommittee) persistNativeBuckets(ctx context.Context, receipt *action.Receipt, err error) error {
   326  	// Start to write native buckets archive after cook and only when the action is executed successfully
   327  	blkCtx := protocol.MustGetBlockCtx(ctx)
   328  	bcCtx := protocol.MustGetBlockchainCtx(ctx)
   329  	featureCtx := protocol.MustGetFeatureWithHeightCtx(ctx)
   330  	rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx))
   331  	epochHeight := rp.GetEpochHeight(rp.GetEpochNum(blkCtx.BlockHeight))
   332  	if !featureCtx.EnableNativeStaking(epochHeight) {
   333  		return nil
   334  	}
   335  	if receipt == nil || receipt.Status != uint64(iotextypes.ReceiptStatus_Success) {
   336  		return nil
   337  	}
   338  	log.L().Info("Store native buckets to election db", zap.Int("size", len(sc.currentNativeBuckets)))
   339  	if err := sc.electionCommittee.PutNativePollByEpoch(
   340  		rp.GetEpochNum(blkCtx.BlockHeight)+1, // The native buckets recorded in this epoch will be used in next one
   341  		bcCtx.Tip.Timestamp,                  // The timestamp of last block is used to represent the current buckets timestamp
   342  		sc.currentNativeBuckets,
   343  	); err != nil {
   344  		return err
   345  	}
   346  	sc.currentNativeBuckets = nil
   347  	return nil
   348  }