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

     1  // Copyright (c) 2019 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 rolldpos
     7  
     8  import (
     9  	"context"
    10  	"strconv"
    11  
    12  	"github.com/pkg/errors"
    13  
    14  	"github.com/iotexproject/iotex-address/address"
    15  	"github.com/iotexproject/iotex-core/action"
    16  	"github.com/iotexproject/iotex-core/action/protocol"
    17  	"github.com/iotexproject/iotex-core/pkg/log"
    18  )
    19  
    20  const protocolID = "rolldpos"
    21  
    22  // Protocol defines an epoch protocol
    23  type Protocol struct {
    24  	numCandidateDelegates   uint64
    25  	numDelegates            uint64
    26  	numSubEpochs            uint64
    27  	numSubEpochsDardanelles uint64
    28  	dardanellesHeight       uint64
    29  	dardanellesOn           bool
    30  }
    31  
    32  // FindProtocol return a registered protocol from registry
    33  func FindProtocol(registry *protocol.Registry) *Protocol {
    34  	if registry == nil {
    35  		return nil
    36  	}
    37  	p, ok := registry.Find(protocolID)
    38  	if !ok {
    39  		return nil
    40  	}
    41  	rp, ok := p.(*Protocol)
    42  	if !ok {
    43  		log.S().Panic("fail to cast rolldpos protocol")
    44  	}
    45  	return rp
    46  }
    47  
    48  // MustGetProtocol return a registered protocol from registry
    49  func MustGetProtocol(registry *protocol.Registry) *Protocol {
    50  	if registry == nil {
    51  		log.S().Panic("registry cannot be nil")
    52  	}
    53  	p, ok := registry.Find(protocolID)
    54  	if !ok {
    55  		log.S().Panic("rolldpos protocol is not registered")
    56  	}
    57  	rp, ok := p.(*Protocol)
    58  	if !ok {
    59  		log.S().Panic("fail to cast to rolldpos protocol")
    60  	}
    61  	return rp
    62  }
    63  
    64  // Option is optional setting for epoch protocol
    65  type Option func(*Protocol) error
    66  
    67  // EnableDardanellesSubEpoch will set give numSubEpochs at give height.
    68  func EnableDardanellesSubEpoch(height, numSubEpochs uint64) Option {
    69  	return func(p *Protocol) error {
    70  		p.dardanellesOn = true
    71  		p.numSubEpochsDardanelles = numSubEpochs
    72  		p.dardanellesHeight = height
    73  		return nil
    74  	}
    75  }
    76  
    77  // NewProtocol returns a new rolldpos protocol
    78  func NewProtocol(numCandidateDelegates, numDelegates, numSubEpochs uint64, opts ...Option) *Protocol {
    79  	if numCandidateDelegates < numDelegates {
    80  		numCandidateDelegates = numDelegates
    81  	}
    82  	p := &Protocol{
    83  		numCandidateDelegates: numCandidateDelegates,
    84  		numDelegates:          numDelegates,
    85  		numSubEpochs:          numSubEpochs,
    86  	}
    87  	for _, opt := range opts {
    88  		if err := opt(p); err != nil {
    89  			log.S().Panicf("Failed to execute epoch protocol creation option %p: %v", opt, err)
    90  		}
    91  	}
    92  	return p
    93  }
    94  
    95  // ProtocolAddr returns the address generated from protocol id
    96  func ProtocolAddr() address.Address {
    97  	return protocol.HashStringToAddress(protocolID)
    98  }
    99  
   100  // Handle handles a modification
   101  func (p *Protocol) Handle(context.Context, action.Action, protocol.StateManager) (*action.Receipt, error) {
   102  	return nil, nil
   103  }
   104  
   105  // ReadState read the state on blockchain via protocol
   106  func (p *Protocol) ReadState(ctx context.Context, sr protocol.StateReader, method []byte, args ...[]byte) ([]byte, uint64, error) {
   107  	tipHeight, err := sr.Height()
   108  	if err != nil {
   109  		return nil, uint64(0), err
   110  	}
   111  	switch string(method) {
   112  	case "NumCandidateDelegates":
   113  		return []byte(strconv.FormatUint(p.numCandidateDelegates, 10)), tipHeight, nil
   114  	case "NumDelegates":
   115  		return []byte(strconv.FormatUint(p.numDelegates, 10)), tipHeight, nil
   116  	case "NumSubEpochs":
   117  		if len(args) != 1 {
   118  			return nil, uint64(0), errors.Errorf("invalid number of arguments %d", len(args))
   119  		}
   120  		height, err := strconv.ParseUint(string(args[0]), 10, 64)
   121  		if err != nil {
   122  			return nil, uint64(0), err
   123  		}
   124  		numSubEpochs := p.NumSubEpochs(height)
   125  		return []byte(strconv.FormatUint(numSubEpochs, 10)), tipHeight, nil
   126  	case "EpochNumber":
   127  		if len(args) != 1 {
   128  			return nil, uint64(0), errors.Errorf("invalid number of arguments %d", len(args))
   129  		}
   130  		height, err := strconv.ParseUint(string(args[0]), 10, 64)
   131  		if err != nil {
   132  			return nil, uint64(0), err
   133  		}
   134  		epochNumber := p.GetEpochNum(height)
   135  		return []byte(strconv.FormatUint(epochNumber, 10)), tipHeight, nil
   136  	case "EpochHeight":
   137  		if len(args) != 1 {
   138  			return nil, uint64(0), errors.Errorf("invalid number of arguments %d", len(args))
   139  		}
   140  		epochNumber, err := strconv.ParseUint(string(args[0]), 10, 64)
   141  		if err != nil {
   142  			return nil, uint64(0), err
   143  		}
   144  		epochHeight := p.GetEpochHeight(epochNumber)
   145  		return []byte(strconv.FormatUint(epochHeight, 10)), tipHeight, nil
   146  	case "EpochLastHeight":
   147  		if len(args) != 1 {
   148  			return nil, uint64(0), errors.Errorf("invalid number of arguments %d", len(args))
   149  		}
   150  		epochNumber, err := strconv.ParseUint(string(args[0]), 10, 64)
   151  		if err != nil {
   152  			return nil, uint64(0), err
   153  		}
   154  		epochLastHeight := p.GetEpochLastBlockHeight(epochNumber)
   155  		return []byte(strconv.FormatUint(epochLastHeight, 10)), tipHeight, nil
   156  	case "SubEpochNumber":
   157  		if len(args) != 1 {
   158  			return nil, uint64(0), errors.Errorf("invalid number of arguments %d", len(args))
   159  		}
   160  		height, err := strconv.ParseUint(string(args[0]), 10, 64)
   161  		if err != nil {
   162  			return nil, uint64(0), err
   163  		}
   164  		subEpochNumber := p.GetSubEpochNum(height)
   165  		return []byte(strconv.FormatUint(subEpochNumber, 10)), tipHeight, nil
   166  	default:
   167  		return nil, tipHeight, errors.New("corresponding method isn't found")
   168  	}
   169  }
   170  
   171  // Register registers the protocol with a unique ID
   172  func (p *Protocol) Register(r *protocol.Registry) error {
   173  	return r.Register(protocolID, p)
   174  }
   175  
   176  // ForceRegister registers the protocol with a unique ID and force replacing the previous protocol if it exists
   177  func (p *Protocol) ForceRegister(r *protocol.Registry) error {
   178  	return r.ForceRegister(protocolID, p)
   179  }
   180  
   181  // Name returns the name of protocol
   182  func (p *Protocol) Name() string {
   183  	return protocolID
   184  }
   185  
   186  // NumCandidateDelegates returns the number of delegate candidates for an epoch
   187  func (p *Protocol) NumCandidateDelegates() uint64 {
   188  	return p.numCandidateDelegates
   189  }
   190  
   191  // NumDelegates returns the number of delegates in an epoch
   192  func (p *Protocol) NumDelegates() uint64 {
   193  	return p.numDelegates
   194  }
   195  
   196  // NumSubEpochs returns the number of subEpochs given a block height
   197  func (p *Protocol) NumSubEpochs(height uint64) uint64 {
   198  	if !p.dardanellesOn || height < p.dardanellesHeight {
   199  		return p.numSubEpochs
   200  	}
   201  	return p.numSubEpochsDardanelles
   202  }
   203  
   204  // GetEpochNum returns the number of the epoch for a given height
   205  func (p *Protocol) GetEpochNum(height uint64) uint64 {
   206  	if height == 0 {
   207  		return 0
   208  	}
   209  	if !p.dardanellesOn || height <= p.dardanellesHeight {
   210  		return (height-1)/p.numDelegates/p.numSubEpochs + 1
   211  	}
   212  	dardanellesEpoch := p.GetEpochNum(p.dardanellesHeight)
   213  	dardanellesEpochHeight := p.GetEpochHeight(dardanellesEpoch)
   214  	return dardanellesEpoch + (height-dardanellesEpochHeight)/p.numDelegates/p.numSubEpochsDardanelles
   215  }
   216  
   217  // GetEpochHeight returns the start height of an epoch
   218  func (p *Protocol) GetEpochHeight(epochNum uint64) uint64 {
   219  	if epochNum == 0 {
   220  		return 0
   221  	}
   222  	dardanellesEpoch := p.GetEpochNum(p.dardanellesHeight)
   223  	if !p.dardanellesOn || epochNum <= dardanellesEpoch {
   224  		return (epochNum-1)*p.numDelegates*p.numSubEpochs + 1
   225  	}
   226  	dardanellesEpochHeight := p.GetEpochHeight(dardanellesEpoch)
   227  	return dardanellesEpochHeight + (epochNum-dardanellesEpoch)*p.numDelegates*p.numSubEpochsDardanelles
   228  }
   229  
   230  // GetEpochLastBlockHeight returns the last height of an epoch
   231  func (p *Protocol) GetEpochLastBlockHeight(epochNum uint64) uint64 {
   232  	return p.GetEpochHeight(epochNum+1) - 1
   233  }
   234  
   235  // GetSubEpochNum returns the sub epoch number of a block height
   236  func (p *Protocol) GetSubEpochNum(height uint64) uint64 {
   237  	return (height - p.GetEpochHeight(p.GetEpochNum(height))) / p.numDelegates
   238  }
   239  
   240  // ProductivityByEpoch read the productivity in an epoch
   241  func (p *Protocol) ProductivityByEpoch(
   242  	epochNum uint64,
   243  	tipHeight uint64,
   244  	productivity func(uint64, uint64) (map[string]uint64, error),
   245  ) (uint64, map[string]uint64, error) {
   246  	if tipHeight == 0 {
   247  		return 0, map[string]uint64{}, nil
   248  	}
   249  	currentEpochNum := p.GetEpochNum(tipHeight)
   250  	if epochNum > currentEpochNum {
   251  		return 0, nil, errors.Errorf("epoch number %d is larger than current epoch number %d", epochNum, currentEpochNum)
   252  	}
   253  	epochStartHeight := p.GetEpochHeight(epochNum)
   254  	var epochEndHeight uint64
   255  	if epochNum == currentEpochNum {
   256  		epochEndHeight = tipHeight
   257  	} else {
   258  		epochEndHeight = p.GetEpochLastBlockHeight(epochNum)
   259  	}
   260  	produce, err := productivity(epochStartHeight, epochEndHeight)
   261  	return epochEndHeight - epochStartHeight + 1, produce, err
   262  }