Merge pull request #1831 from rlan35/staking_tx

Delegator-validators index and collect rewards state transition.
pull/1833/head
Rongjian Lan 5 years ago committed by GitHub
commit b9479fb835
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 58
      contracts/contract_caller.go
  2. 299
      core/blockchain.go
  3. 3
      core/evm.go
  4. 26
      core/rawdb/accessors_chain.go
  5. 6
      core/rawdb/schema.go
  6. 6
      core/state/statedb.go
  7. 2
      core/state_processor.go
  8. 111
      core/state_transition.go
  9. 1
      internal/chain/engine.go
  10. 6
      node/node.go
  11. 8
      staking/types/delegation.go
  12. 51
      staking/types/validator.go

@ -1,58 +0,0 @@
package contracts
import (
"sync"
"github.com/harmony-one/harmony/internal/utils"
"github.com/ethereum/go-ethereum/common/math"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
)
// ContractCaller is used to call smart contract locally
type ContractCaller struct {
blockchain *core.BlockChain // Ethereum blockchain to handle the consensus
mu sync.Mutex
config *params.ChainConfig
}
// NewContractCaller initialize a new contract caller.
func NewContractCaller(bc *core.BlockChain, config *params.ChainConfig) *ContractCaller {
cc := ContractCaller{}
cc.blockchain = bc
cc.mu = sync.Mutex{}
cc.config = config
return &cc
}
// CallContract calls a contracts with the specified transaction.
func (cc *ContractCaller) CallContract(tx *types.Transaction) ([]byte, error) {
currBlock := cc.blockchain.CurrentBlock()
msg, err := tx.AsMessage(types.MakeSigner(cc.config, currBlock.Header().Epoch()))
if err != nil {
utils.Logger().Error().Err(err).Msg("[ABI] Failed to convert transaction to message")
return []byte{}, err
}
evmContext := core.NewEVMContext(msg, currBlock.Header(), cc.blockchain, nil)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
stateDB, err := cc.blockchain.State()
if err != nil {
utils.Logger().Error().Err(err).Msg("[ABI] Failed to retrieve state db")
return []byte{}, err
}
vmenv := vm.NewEVM(evmContext, stateDB, cc.config, vm.Config{})
gaspool := new(core.GasPool).AddGas(math.MaxUint64)
returnValue, _, failed, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
if err != nil || failed {
utils.Logger().Error().Err(err).Msg("[ABI] Failed executing the transaction")
return []byte{}, err
}
return returnValue, nil
}

@ -18,6 +18,7 @@
package core
import (
"bytes"
"errors"
"fmt"
"io"
@ -58,19 +59,20 @@ var (
)
const (
bodyCacheLimit = 256
blockCacheLimit = 256
receiptsCacheLimit = 32
maxFutureBlocks = 256
maxTimeFutureBlocks = 30
badBlockLimit = 10
triesInMemory = 128
shardCacheLimit = 2
commitsCacheLimit = 10
epochCacheLimit = 10
randomnessCacheLimit = 10
stakingCacheLimit = 256
validatorListCacheLimit = 2
bodyCacheLimit = 256
blockCacheLimit = 256
receiptsCacheLimit = 32
maxFutureBlocks = 256
maxTimeFutureBlocks = 30
badBlockLimit = 10
triesInMemory = 128
shardCacheLimit = 2
commitsCacheLimit = 10
epochCacheLimit = 10
randomnessCacheLimit = 10
stakingCacheLimit = 256
validatorListCacheLimit = 2
validatorListByDelegatorCacheLimit = 256
// BlockChainVersion ensures that an incompatible database forces a resync from scratch.
BlockChainVersion = 3
@ -123,18 +125,19 @@ type BlockChain struct {
currentBlock atomic.Value // Current head of the block chain
currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!)
stateCache state.Database // State database to reuse between imports (contains state cache)
bodyCache *lru.Cache // Cache for the most recent block bodies
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
receiptsCache *lru.Cache // Cache for the most recent receipts per block
blockCache *lru.Cache // Cache for the most recent entire blocks
futureBlocks *lru.Cache // future blocks are blocks added for later processing
shardStateCache *lru.Cache
lastCommitsCache *lru.Cache
epochCache *lru.Cache // Cache epoch number → first block number
randomnessCache *lru.Cache // Cache for vrf/vdf
stakingCache *lru.Cache // Cache for staking validator
validatorListCache *lru.Cache // Cache of validator list
stateCache state.Database // State database to reuse between imports (contains state cache)
bodyCache *lru.Cache // Cache for the most recent block bodies
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
receiptsCache *lru.Cache // Cache for the most recent receipts per block
blockCache *lru.Cache // Cache for the most recent entire blocks
futureBlocks *lru.Cache // future blocks are blocks added for later processing
shardStateCache *lru.Cache
lastCommitsCache *lru.Cache
epochCache *lru.Cache // Cache epoch number → first block number
randomnessCache *lru.Cache // Cache for vrf/vdf
stakingCache *lru.Cache // Cache for staking validator
validatorListCache *lru.Cache // Cache of validator list
validatorListByDelegatorCache *lru.Cache // Cache of validator list by delegator
quit chan struct{} // blockchain quit channel
running int32 // running must be called atomically
@ -173,29 +176,31 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
randomnessCache, _ := lru.New(randomnessCacheLimit)
stakingCache, _ := lru.New(stakingCacheLimit)
validatorListCache, _ := lru.New(validatorListCacheLimit)
validatorListByDelegatorCache, _ := lru.New(validatorListByDelegatorCacheLimit)
bc := &BlockChain{
chainConfig: chainConfig,
cacheConfig: cacheConfig,
db: db,
triegc: prque.New(nil),
stateCache: state.NewDatabase(db),
quit: make(chan struct{}),
shouldPreserve: shouldPreserve,
bodyCache: bodyCache,
bodyRLPCache: bodyRLPCache,
receiptsCache: receiptsCache,
blockCache: blockCache,
futureBlocks: futureBlocks,
shardStateCache: shardCache,
lastCommitsCache: commitsCache,
epochCache: epochCache,
randomnessCache: randomnessCache,
stakingCache: stakingCache,
validatorListCache: validatorListCache,
engine: engine,
vmConfig: vmConfig,
badBlocks: badBlocks,
chainConfig: chainConfig,
cacheConfig: cacheConfig,
db: db,
triegc: prque.New(nil),
stateCache: state.NewDatabase(db),
quit: make(chan struct{}),
shouldPreserve: shouldPreserve,
bodyCache: bodyCache,
bodyRLPCache: bodyRLPCache,
receiptsCache: receiptsCache,
blockCache: blockCache,
futureBlocks: futureBlocks,
shardStateCache: shardCache,
lastCommitsCache: commitsCache,
epochCache: epochCache,
randomnessCache: randomnessCache,
stakingCache: stakingCache,
validatorListCache: validatorListCache,
validatorListByDelegatorCache: validatorListByDelegatorCache,
engine: engine,
vmConfig: vmConfig,
badBlocks: badBlocks,
}
bc.SetValidator(NewBlockValidator(chainConfig, bc, engine))
bc.SetProcessor(NewStateProcessor(chainConfig, bc, engine))
@ -1068,6 +1073,8 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
}
// Write other block data using a batch.
// TODO: put following into a func
/////////////////////////// START
batch := bc.db.NewBatch()
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
@ -1083,25 +1090,90 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
err := rawdb.WriteCXReceipts(batch, uint32(i), block.NumberU64(), block.Hash(), shardReceipts, false)
if err != nil {
utils.Logger().Debug().Err(err).Interface("shardReceipts", shardReceipts).Int("toShardID", i).Msg("WriteCXReceipts cannot write into database")
return NonStatTy, err
}
}
// Mark incomingReceipts in the block as spent
bc.WriteCXReceiptsProofSpent(block.IncomingReceipts())
}
//check non zero VRF field in header and add to local db
if len(block.Vrf()) > 0 {
vrfBlockNumbers, _ := bc.ReadEpochVrfBlockNums(block.Header().Epoch())
if (len(vrfBlockNumbers) > 0) && (vrfBlockNumbers[len(vrfBlockNumbers)-1] == block.NumberU64()) {
utils.Logger().Error().
Str("number", block.Number().String()).
Str("epoch", block.Header().Epoch().String()).
Msg("VRF block number is already in local db")
} else {
vrfBlockNumbers = append(vrfBlockNumbers, block.NumberU64())
err = bc.WriteEpochVrfBlockNums(block.Header().Epoch(), vrfBlockNumbers)
if err != nil {
utils.Logger().Error().
Str("number", block.Number().String()).
Str("epoch", block.Header().Epoch().String()).
Msg("failed to write VRF block number to local db")
return NonStatTy, err
}
}
}
//check non zero Vdf in header and add to local db
if len(block.Vdf()) > 0 {
err = bc.WriteEpochVdfBlockNum(block.Header().Epoch(), block.Number())
if err != nil {
utils.Logger().Error().
Str("number", block.Number().String()).
Str("epoch", block.Header().Epoch().String()).
Msg("failed to write VDF block number to local db")
return NonStatTy, err
}
}
header := block.Header()
if header.ShardStateHash() != (common.Hash{}) {
epoch := new(big.Int).Add(header.Epoch(), common.Big1)
err = bc.WriteShardStateBytes(batch, epoch, header.ShardState())
if err != nil {
header.Logger(utils.Logger()).Warn().Err(err).Msg("cannot store shard state")
return NonStatTy, err
}
}
if len(header.CrossLinks()) > 0 {
crossLinks := &types.CrossLinks{}
err = rlp.DecodeBytes(header.CrossLinks(), crossLinks)
if err != nil {
header.Logger(utils.Logger()).Warn().Err(err).Msg("[insertChain] cannot parse cross links")
return NonStatTy, err
}
if !crossLinks.IsSorted() {
header.Logger(utils.Logger()).Warn().Err(err).Msg("[insertChain] cross links are not sorted")
return NonStatTy, errors.New("proposed cross links are not sorted")
}
for _, crossLink := range *crossLinks {
if err := bc.WriteCrossLinks(types.CrossLinks{crossLink}, false); err == nil {
utils.Logger().Info().Uint64("blockNum", crossLink.BlockNum().Uint64()).Uint32("shardID", crossLink.ShardID()).Msg("[InsertChain] Cross Link Added to Beaconchain")
}
bc.DeleteCrossLinks(types.CrossLinks{crossLink}, true)
bc.WriteShardLastCrossLink(crossLink.ShardID(), crossLink)
}
}
if bc.chainConfig.IsStaking(block.Epoch()) {
for _, tx := range block.StakingTransactions() {
err = bc.UpdateValidatorList(tx)
err = bc.UpdateStakingMetaData(tx)
// keep offchain database consistency with onchain we need revert
// but it should not happend unless local database corrupted
if err != nil {
utils.Logger().Debug().Msgf("oops, UpdateValidatorList failed, err: %+v", err)
utils.Logger().Debug().Msgf("oops, UpdateStakingMetaData failed, err: %+v", err)
return NonStatTy, err
}
}
}
/////////////////////////// END
// If the total difficulty is higher than our known, add it to the canonical chain
// Second clause in the if statement reduces the vulnerability to selfish mining.
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
@ -1145,49 +1217,6 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
// After insertion is done, all accumulated events will be fired.
func (bc *BlockChain) InsertChain(chain types.Blocks, verifyHeaders bool) (int, error) {
n, events, logs, err := bc.insertChain(chain, verifyHeaders)
if err == nil {
// TODO: incorporate these into insertChain
for idx, block := range chain {
header := block.Header()
header.Logger(utils.Logger()).Info().
Int("segmentIndex", idx).
Str("parentHash", header.ParentHash().Hex()).
Msg("added block to chain")
// TODO: move into WriteBlockWithState
if header.ShardStateHash() != (common.Hash{}) {
epoch := new(big.Int).Add(header.Epoch(), common.Big1)
err = bc.WriteShardStateBytes(epoch, header.ShardState())
if err != nil {
header.Logger(utils.Logger()).Warn().Err(err).Msg("cannot store shard state")
return n, err
}
}
// TODO: move into WriteBlockWithState
if len(header.CrossLinks()) > 0 {
crossLinks := &types.CrossLinks{}
err = rlp.DecodeBytes(header.CrossLinks(), crossLinks)
if err != nil {
header.Logger(utils.Logger()).Warn().Err(err).Msg("[insertChain] cannot parse cross links")
return n, err
}
if !crossLinks.IsSorted() {
header.Logger(utils.Logger()).Warn().Err(err).Msg("[insertChain] cross links are not sorted")
return n, errors.New("proposed cross links are not sorted")
}
for _, crossLink := range *crossLinks {
if err := bc.WriteCrossLinks(types.CrossLinks{crossLink}, false); err == nil {
utils.Logger().Info().Uint64("blockNum", crossLink.BlockNum().Uint64()).Uint32("shardID", crossLink.ShardID()).Msg("[InsertChain] Cross Link Added to Beaconchain")
}
bc.DeleteCrossLinks(types.CrossLinks{crossLink}, true)
bc.WriteShardLastCrossLink(crossLink.ShardID(), crossLink)
}
}
}
}
// This should be done after everything about adding a block is done.
bc.PostChainEvents(events, logs)
return n, err
}
@ -1396,37 +1425,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifyHeaders bool) (int,
cache, _ := bc.stateCache.TrieDB().Size()
stats.report(chain, i, cache)
//check non zero VRF field in header and add to local db
if len(block.Vrf()) > 0 {
vrfBlockNumbers, _ := bc.ReadEpochVrfBlockNums(block.Header().Epoch())
if (len(vrfBlockNumbers) > 0) && (vrfBlockNumbers[len(vrfBlockNumbers)-1] == block.NumberU64()) {
utils.Logger().Error().
Str("number", chain[i].Number().String()).
Str("epoch", block.Header().Epoch().String()).
Msg("VRF block number is already in local db")
} else {
vrfBlockNumbers = append(vrfBlockNumbers, block.NumberU64())
err = bc.WriteEpochVrfBlockNums(block.Header().Epoch(), vrfBlockNumbers)
if err != nil {
utils.Logger().Error().
Str("number", chain[i].Number().String()).
Str("epoch", block.Header().Epoch().String()).
Msg("failed to write VRF block number to local db")
}
}
}
//check non zero Vdf in header and add to local db
if len(block.Vdf()) > 0 {
err = bc.WriteEpochVdfBlockNum(block.Header().Epoch(), block.Number())
if err != nil {
utils.Logger().Error().
Str("number", chain[i].Number().String()).
Str("epoch", block.Header().Epoch().String()).
Msg("failed to write VDF block number to local db")
}
}
}
// Append a single chain head event if we've progressed the chain
if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() {
@ -1870,14 +1868,14 @@ func (bc *BlockChain) WriteShardState(
}
// WriteShardStateBytes saves the given sharding state under the given epoch number.
func (bc *BlockChain) WriteShardStateBytes(
func (bc *BlockChain) WriteShardStateBytes(db rawdb.DatabaseWriter,
epoch *big.Int, shardState []byte,
) error {
decodeShardState := shard.State{}
if err := rlp.DecodeBytes(shardState, &decodeShardState); err != nil {
return err
}
err := rawdb.WriteShardStateBytes(bc.db, epoch, shardState)
err := rawdb.WriteShardStateBytes(db, epoch, shardState)
if err != nil {
return err
}
@ -2325,16 +2323,41 @@ func (bc *BlockChain) WriteValidatorList(addrs []common.Address) error {
if err != nil {
return err
}
by, err := rlp.EncodeToBytes(addrs)
bytes, err := rlp.EncodeToBytes(addrs)
if err == nil {
bc.validatorListCache.Add("validatorList", bytes)
}
return nil
}
// ReadValidatorListByDelegator reads the addresses of validators delegated by a delegator
func (bc *BlockChain) ReadValidatorListByDelegator(delegator common.Address) ([]common.Address, error) {
if cached, ok := bc.validatorListByDelegatorCache.Get(delegator.Bytes()); ok {
by := cached.([]byte)
m := []common.Address{}
if err := rlp.DecodeBytes(by, &m); err != nil {
return nil, err
}
return m, nil
}
return rawdb.ReadValidatorListByDelegator(bc.db, delegator)
}
// WriteValidatorListByDelegator writes the list of validator addresses to database
func (bc *BlockChain) WriteValidatorListByDelegator(delegator common.Address, addrs []common.Address) error {
err := rawdb.WriteValidatorListByDelegator(bc.db, delegator, addrs)
if err != nil {
return err
}
bc.validatorListCache.Add("validatorList", by)
bytes, err := rlp.EncodeToBytes(addrs)
if err == nil {
bc.validatorListByDelegatorCache.Add(delegator.Bytes(), bytes)
}
return nil
}
// UpdateValidatorList updates the validator map according to staking transaction
func (bc *BlockChain) UpdateValidatorList(tx *staking.StakingTransaction) error {
// UpdateStakingMetaData updates the validator's and the delegator's meta data according to staking transaction
func (bc *BlockChain) UpdateStakingMetaData(tx *staking.StakingTransaction) error {
// TODO: simply the logic here in staking/types/transaction.go
payload, err := tx.RLPEncodeStakeMsg()
if err != nil {
@ -2365,8 +2388,26 @@ func (bc *BlockChain) UpdateValidatorList(tx *staking.StakingTransaction) error
// following cases are placeholder for now
case staking.DirectiveEditValidator:
case staking.DirectiveDelegate:
delegate := decodePayload.(*staking.Delegate)
validators, err := bc.ReadValidatorListByDelegator(delegate.DelegatorAddress)
if err != nil {
return err
}
found := false
for _, validator := range validators {
if bytes.Compare(validator.Bytes(), delegate.ValidatorAddress.Bytes()) == 0 {
found = true
break
}
}
if !found {
validators = append(validators, delegate.ValidatorAddress)
}
err = bc.WriteValidatorListByDelegator(delegate.DelegatorAddress, validators)
return err
case staking.DirectiveUndelegate:
case staking.DirectiveCollectRewards:
// TODO: Check whether the delegation reward can be cleared after reward is collected
default:
}
return nil

@ -35,6 +35,9 @@ type ChainContext interface {
// GetHeader returns the hash corresponding to their hash.
GetHeader(common.Hash, uint64) *block.Header
// ReadValidatorListByDelegator returns the validators list of a delegator
ReadValidatorListByDelegator(common.Address) ([]common.Address, error)
}
// NewEVMContext creates a new context for use in the EVM.

@ -666,3 +666,29 @@ func WriteValidatorList(db DatabaseWriter, addrs []common.Address) error {
}
return err
}
// ReadValidatorListByDelegator retrieves the list of validators delegated by a delegator
func ReadValidatorListByDelegator(db DatabaseReader, delegator common.Address) ([]common.Address, error) {
data, err := db.Get(delegatorValidatorListKey(delegator))
if len(data) == 0 || err != nil {
return []common.Address{}, nil
}
addrs := []common.Address{}
if err := rlp.DecodeBytes(data, &addrs); err != nil {
utils.Logger().Error().Err(err).Msg("Unable to Decode validator List from database")
return nil, err
}
return addrs, nil
}
// WriteValidatorListByDelegator stores the list of validators delegated by a delegator
func WriteValidatorListByDelegator(db DatabaseWriter, delegator common.Address, addrs []common.Address) error {
bytes, err := rlp.EncodeToBytes(addrs)
if err != nil {
utils.Logger().Error().Msg("[WriteValidatorListByDelegator] Failed to encode")
}
if err := db.Put(delegatorValidatorListKey(delegator), bytes); err != nil {
utils.Logger().Error().Msg("[WriteValidatorListByDelegator] Failed to store to database")
}
return err
}

@ -65,6 +65,8 @@ var (
crosslinkPrefix = []byte("cl") // prefix for crosslink
tempCrosslinkPrefix = []byte("tcl") // prefix for tempCrosslink
delegatorValidatorListPrefix = []byte("dvl") // prefix for delegator's validator list
// TODO: shorten the key prefix so we don't waste db space
cxReceiptPrefix = []byte("cxReceipt") // prefix for cross shard transaction receipt
tempCxReceiptPrefix = []byte("tempCxReceipt") // prefix for temporary cross shard transaction receipt
@ -201,6 +203,10 @@ func crosslinkKey(shardID uint32, blockNum uint64, temp bool) []byte {
return key
}
func delegatorValidatorListKey(delegator common.Address) []byte {
return append(delegatorValidatorListPrefix, delegator.Bytes()...)
}
// cxReceiptKey = cxReceiptsPrefix + shardID + num (uint64 big endian) + hash
func cxReceiptKey(shardID uint32, number uint64, hash common.Hash, temp bool) []byte {
prefix := cxReceiptPrefix

@ -701,6 +701,10 @@ func (db *DB) GetStakingInfo(addr common.Address) *stk.ValidatorWrapper {
// UpdateStakingInfo update staking information of a given validator (including delegation info)
func (db *DB) UpdateStakingInfo(addr common.Address, val *stk.ValidatorWrapper) error {
if err := val.SanityCheck(); err != nil {
return err
}
by, err := rlp.EncodeToBytes(val)
if err != nil {
return err
@ -766,6 +770,6 @@ func (db *DB) AddReward(validator common.Address, reward *big.Int) error {
// CollectReward moves the rewards into the delegator's normal balance.
func (db *DB) CollectReward(delegator common.Address) {
//TODO: implement collect reward logic
// The reward will be withdrawn to the delegator's address balance as a whole.
}

@ -218,7 +218,7 @@ func ApplyStakingTransaction(
vmenv := vm.NewEVM(context, statedb, config, cfg)
// Apply the transaction to the current state (included in the env)
gas, err = ApplyStakingMessage(vmenv, msg, gp)
gas, err = ApplyStakingMessage(vmenv, msg, gp, bc)
utils.Logger().Info().Msgf("ApplyStakingMessage: usedGas: %v, err: %v", gas, err)
// even there is error, we charge it

@ -37,6 +37,12 @@ var (
errValidatorExist = errors.New("staking validator already exists")
errValidatorNotExist = errors.New("staking validator does not exist")
errNoDelegationToUndelegate = errors.New("no delegation to undelegate")
errInvalidSelfDelegation = errors.New("self delegation can not be less than min_self_delegation")
errInvalidTotalDelegation = errors.New("total delegation can not be bigger than max_total_delegation")
errMinSelfDelegationTooSmall = errors.New("min_self_delegation has to be greater than 1 ONE")
errInvalidMaxTotalDelegation = errors.New("max_total_delegation can not be less than min_self_delegation")
errCommissionRateChangeTooFast = errors.New("commission rate can not be changed more than MaxChangeRate within the same epoch")
errCommissionRateChangeTooHigh = errors.New("commission rate can not be higher than MaxCommissionRate")
)
/*
@ -66,6 +72,7 @@ type StateTransition struct {
data []byte
state vm.StateDB
evm *vm.EVM
bc ChainContext
}
// Message represents a message sent to a contract.
@ -119,7 +126,7 @@ func IntrinsicGas(data []byte, contractCreation, homestead bool) (uint64, error)
}
// NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition {
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool, bc ChainContext) *StateTransition {
return &StateTransition{
gp: gp,
evm: evm,
@ -128,6 +135,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
value: msg.Value(),
data: msg.Data(),
state: evm.StateDB,
bc: bc,
}
}
@ -139,12 +147,12 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
// indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block.
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) {
return NewStateTransition(evm, msg, gp).TransitionDb()
return NewStateTransition(evm, msg, gp, nil).TransitionDb()
}
// ApplyStakingMessage computes the new state for staking message
func ApplyStakingMessage(evm *vm.EVM, msg Message, gp *GasPool) (uint64, error) {
return NewStateTransition(evm, msg, gp).StakingTransitionDb()
func ApplyStakingMessage(evm *vm.EVM, msg Message, gp *GasPool, bc ChainContext) (uint64, error) {
return NewStateTransition(evm, msg, gp, bc).StakingTransitionDb()
}
// to returns the recipient of the message.
@ -279,7 +287,7 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) {
homestead := st.evm.ChainConfig().IsS3(st.evm.EpochNumber) // s3 includes homestead
// Pay intrinsic gas
// TODO: add new formula for staking transaction
// TODO: propose staking-specific formula for staking transaction
gas, err := IntrinsicGas(st.data, false, homestead)
if err != nil {
return 0, err
@ -320,7 +328,11 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) {
}
err = st.applyUndelegateTx(stkMsg)
case types.CollectRewards:
stkMsg := &staking.CollectRewards{}
if err = rlp.DecodeBytes(msg.Data(), stkMsg); err != nil {
return 0, err
}
err = st.applyCollectRewards(stkMsg)
default:
return 0, staking.ErrInvalidStakingKind
}
@ -329,18 +341,21 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) {
return st.gasUsed(), err
}
func (st *StateTransition) applyCreateValidatorTx(nv *staking.CreateValidator, blockNum *big.Int) error {
if st.state.IsValidator(nv.ValidatorAddress) {
func (st *StateTransition) applyCreateValidatorTx(createValidator *staking.CreateValidator, blockNum *big.Int) error {
if st.state.IsValidator(createValidator.ValidatorAddress) {
return errValidatorExist
}
// TODO: move balance into staking
v, err := staking.CreateValidatorFromNewMsg(nv)
v, err := staking.CreateValidatorFromNewMsg(createValidator, blockNum)
if err != nil {
return err
}
v.UpdateHeight = blockNum
v.CreationHeight = blockNum
wrapper := staking.ValidatorWrapper{*v, nil, nil, nil}
delegations := []staking.Delegation{}
delegations = append(delegations, staking.NewDelegation(v.Address, createValidator.Amount))
wrapper := staking.ValidatorWrapper{*v, delegations, nil, nil}
if err := st.state.UpdateStakingInfo(v.Address, &wrapper); err != nil {
return err
}
@ -348,26 +363,36 @@ func (st *StateTransition) applyCreateValidatorTx(nv *staking.CreateValidator, b
return nil
}
func (st *StateTransition) applyEditValidatorTx(ev *staking.EditValidator, blockNum *big.Int) error {
if !st.state.IsValidator(ev.ValidatorAddress) {
func (st *StateTransition) applyEditValidatorTx(editValidator *staking.EditValidator, blockNum *big.Int) error {
if !st.state.IsValidator(editValidator.ValidatorAddress) {
return errValidatorNotExist
}
wrapper := st.state.GetStakingInfo(ev.ValidatorAddress)
wrapper := st.state.GetStakingInfo(editValidator.ValidatorAddress)
if wrapper == nil {
return errValidatorNotExist
}
oldRate := wrapper.Validator.Rate
if err := staking.UpdateValidatorFromEditMsg(&wrapper.Validator, ev); err != nil {
if err := staking.UpdateValidatorFromEditMsg(&wrapper.Validator, editValidator); err != nil {
return err
}
newRate := wrapper.Validator.Rate
// update the commision rate change height
// TODO: make sure the rule of MaxChangeRate is not violated
if oldRate.IsNil() || (!newRate.IsNil() && !oldRate.Equal(newRate)) {
// TODO: Use snapshot of validator in this epoch.
rateAtBeginningOfEpoch := wrapper.Validator.Rate
if rateAtBeginningOfEpoch.IsNil() || (!newRate.IsNil() && !rateAtBeginningOfEpoch.Equal(newRate)) {
wrapper.Validator.UpdateHeight = blockNum
}
if err := st.state.UpdateStakingInfo(ev.ValidatorAddress, wrapper); err != nil {
if newRate.Sub(rateAtBeginningOfEpoch).GT(wrapper.Validator.MaxChangeRate) {
return errCommissionRateChangeTooFast
}
if newRate.GT(wrapper.Validator.MaxRate) {
return errCommissionRateChangeTooHigh
}
if err := st.state.UpdateStakingInfo(editValidator.ValidatorAddress, wrapper); err != nil {
return err
}
return nil
@ -394,7 +419,6 @@ func (st *StateTransition) applyDelegateTx(delegate *staking.Delegate) error {
// Firstly use the tokens in undelegation to delegate (redelegate)
undelegateAmount := big.NewInt(0).Set(delegate.Amount)
// Use the latest undelegated token first as it has the longest remaining locking time.
// TODO: always order the list by epoch.
i := len(delegation.Entries) - 1
for ; i >= 0; i-- {
if delegation.Entries[i].Amount.Cmp(undelegateAmount) <= 0 {
@ -421,7 +445,7 @@ func (st *StateTransition) applyDelegateTx(delegate *staking.Delegate) error {
if !delegatorExist {
if CanTransfer(stateDB, delegate.DelegatorAddress, delegate.Amount) {
newDelegator := staking.Delegation{DelegatorAddress: delegate.DelegatorAddress, Amount: delegate.Amount}
newDelegator := staking.NewDelegation(delegate.DelegatorAddress, delegate.Amount)
wrapper.Delegations = append(wrapper.Delegations, newDelegator)
if err := stateDB.UpdateStakingInfo(wrapper.Validator.Address, wrapper); err == nil {
@ -465,3 +489,42 @@ func (st *StateTransition) applyUndelegateTx(undelegate *staking.Undelegate) err
// TODO: do undelegated token distribution after locking period. (in leader block proposal phase)
return nil
}
func (st *StateTransition) applyCollectRewards(collectRewards *staking.CollectRewards) error {
if st.bc == nil {
return errors.New("[CollectRewards] No chain context provided")
}
chainContext := st.bc
validators, err := chainContext.ReadValidatorListByDelegator(collectRewards.DelegatorAddress)
if err != nil {
return err
}
totalRewards := big.NewInt(0)
for i := range validators {
wrapper := st.state.GetStakingInfo(validators[i])
if wrapper == nil {
return errValidatorNotExist
}
// TODO: add the index of the validator-delegation position in the ReadValidatorListByDelegator record to avoid looping
for j := range wrapper.Delegations {
delegation := wrapper.Delegations[j]
if bytes.Equal(delegation.DelegatorAddress.Bytes(), collectRewards.DelegatorAddress.Bytes()) {
if delegation.Reward.Cmp(big.NewInt(0)) > 0 {
totalRewards.Add(totalRewards, delegation.Reward)
}
delegation.Reward.SetUint64(0)
break
}
}
err = st.state.UpdateStakingInfo(wrapper.Validator.Address, wrapper)
if err != nil {
return err
}
}
st.state.AddBalance(collectRewards.DelegatorAddress, totalRewards)
return nil
}

@ -187,7 +187,6 @@ func (e *engineImpl) Finalize(
delegation := wrapper.Delegations[i]
totalWithdraw := big.NewInt(0)
count := 0
// TODO: need ot make sure the entries are ordered by epoch
for j := range delegation.Entries {
if delegation.Entries[j].Epoch.Cmp(header.Epoch()) > 14 { // need to wait at least 14 epochs to withdraw;
totalWithdraw.Add(totalWithdraw, delegation.Entries[j].Amount)

@ -18,7 +18,6 @@ import (
"github.com/harmony-one/harmony/block"
"github.com/harmony-one/harmony/consensus"
"github.com/harmony-one/harmony/consensus/reward"
"github.com/harmony-one/harmony/contracts"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/drand"
@ -213,9 +212,6 @@ type Node struct {
// map of service type to its message channel.
serviceMessageChan map[service.Type]chan *msg_pb.Message
// Used to call smart contract locally
ContractCaller *contracts.ContractCaller
accountManager *accounts.Manager
// Next shard state
@ -449,7 +445,6 @@ func New(host p2p.Host, consensusObj *consensus.Consensus, chainDBFactory shardc
node.Consensus.SetBlockNum(blockchain.CurrentBlock().NumberU64() + 1)
// Add Faucet contract to all shards, so that on testnet, we can demo wallet in explorer
// TODO (leo): we need to have support of cross-shard tx later so that the token can be transferred from beacon chain shard to other tx shards.
if node.NodeConfig.GetNetworkType() != nodeconfig.Mainnet {
if node.isFirstTime {
// Setup one time smart contracts
@ -457,7 +452,6 @@ func New(host p2p.Host, consensusObj *consensus.Consensus, chainDBFactory shardc
} else {
node.AddContractKeyAndAddress(scFaucet)
}
node.ContractCaller = contracts.NewContractCaller(node.Blockchain(), node.Blockchain().Config())
// Create test keys. Genesis will later need this.
var err error
node.TestBankKeys, err = CreateTestBankKeys(TestAccountNumber)

@ -3,6 +3,7 @@ package types
import (
"errors"
"math/big"
"sort"
"github.com/ethereum/go-ethereum/common"
)
@ -59,7 +60,14 @@ func (d *Delegation) Undelegate(epoch *big.Int, amt *big.Int) error {
if !exist {
item := UndelegationEntry{amt, epoch}
d.Entries = append(d.Entries, &item)
// Always sort the undelegate by epoch in increasing order
sort.SliceStable(
d.Entries,
func(i, j int) bool { return d.Entries[i].Epoch.Cmp(d.Entries[j].Epoch) < 0 },
)
}
return nil
}

@ -5,6 +5,8 @@ import (
"fmt"
"math/big"
"github.com/harmony-one/harmony/common/denominations"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
@ -23,7 +25,11 @@ const (
)
var (
errAddressNotMatch = errors.New("Validator key not match")
errAddressNotMatch = errors.New("Validator key not match")
errInvalidSelfDelegation = errors.New("self delegation can not be less than min_self_delegation")
errInvalidTotalDelegation = errors.New("total delegation can not be bigger than max_total_delegation")
errMinSelfDelegationTooSmall = errors.New("min_self_delegation has to be greater than 1 ONE")
errInvalidMaxTotalDelegation = errors.New("max_total_delegation can not be less than min_self_delegation")
)
// ValidatorWrapper contains validator and its delegation information
@ -77,6 +83,32 @@ func (w *ValidatorWrapper) TotalDelegation() *big.Int {
return total
}
// SanityCheck checks the basic requirements
func (w *ValidatorWrapper) SanityCheck() error {
// MinSelfDelegation must be >= 1 ONE
if w.Validator.MinSelfDelegation.Cmp(big.NewInt(denominations.One)) < 0 {
return errMinSelfDelegationTooSmall
}
// MaxTotalDelegation must not be less than MinSelfDelegation
if w.Validator.MaxTotalDelegation.Cmp(w.Validator.MinSelfDelegation) < 0 {
return errInvalidMaxTotalDelegation
}
selfDelegation := w.Delegations[0].Amount
// Self delegation must be >= MinSelfDelegation
if selfDelegation.Cmp(w.Validator.MinSelfDelegation) < 0 {
return errInvalidSelfDelegation
}
totalDelegation := w.TotalDelegation()
// Total delegation must be <= MaxTotalDelegation
if totalDelegation.Cmp(w.Validator.MaxTotalDelegation) > 0 {
return errInvalidTotalDelegation
}
return nil
}
// Description - some possible IRL connections
type Description struct {
Name string `json:"name" yaml:"name"` // name
@ -158,18 +190,18 @@ func (v *Validator) GetCommissionRate() numeric.Dec { return v.Commission.Rate }
func (v *Validator) GetMinSelfDelegation() *big.Int { return v.MinSelfDelegation }
// CreateValidatorFromNewMsg creates validator from NewValidator message
func CreateValidatorFromNewMsg(val *CreateValidator) (*Validator, error) {
func CreateValidatorFromNewMsg(val *CreateValidator, blockNum *big.Int) (*Validator, error) {
desc, err := UpdateDescription(val.Description)
if err != nil {
return nil, err
}
commission := Commission{val.CommissionRates, new(big.Int)}
commission := Commission{val.CommissionRates, blockNum}
pubKeys := []shard.BlsPublicKey{}
pubKeys = append(pubKeys, val.SlotPubKeys...)
// TODO: a new validator should have a minimum of 1 token as self delegation, and that should be added as a delegation entry here.
v := Validator{val.ValidatorAddress, pubKeys,
val.Amount, new(big.Int), val.MinSelfDelegation, val.MaxTotalDelegation, false,
commission, desc, big.NewInt(0)}
commission, desc, blockNum}
return &v, nil
}
@ -188,27 +220,25 @@ func UpdateValidatorFromEditMsg(validator *Validator, edit *EditValidator) error
if edit.CommissionRate != nil {
validator.Rate = *edit.CommissionRate
if err != nil {
return err
}
//TODO update other rates
}
if edit.MinSelfDelegation != nil {
// TODO: add condition that minSelfDelegation can not be higher than the current self delegation
validator.MinSelfDelegation = edit.MinSelfDelegation
}
if edit.MaxTotalDelegation != nil {
// TODO: add condition that MaxTotalDelegation can not be lower than the current total delegation
validator.MaxTotalDelegation = edit.MaxTotalDelegation
}
if edit.SlotKeyToAdd != nil {
found := false
for _, key := range validator.SlotPubKeys {
if key == *edit.SlotKeyToAdd {
found = true
break
}
}
if !found {
validator.SlotPubKeys = append(validator.SlotPubKeys, *edit.SlotKeyToAdd)
}
}
@ -218,6 +248,7 @@ func UpdateValidatorFromEditMsg(validator *Validator, edit *EditValidator) error
for i, key := range validator.SlotPubKeys {
if key == *edit.SlotKeyToRemove {
index = i
break
}
}
// we found key to be removed

Loading…
Cancel
Save