diff --git a/consensus/engine/consensus_engine.go b/consensus/engine/consensus_engine.go index 5e316a353..03bbe93d9 100644 --- a/consensus/engine/consensus_engine.go +++ b/consensus/engine/consensus_engine.go @@ -77,9 +77,7 @@ type Engine interface { // and assembles the final block. // Note: The block header and state database might be updated to reflect any // consensus rules that happen at finalization (e.g. block rewards). - Finalize( - chain ChainReader, header *block.Header, state *state.DB, - txs []*types.Transaction, + Finalize(chain ChainReader, header *block.Header, state *state.DB, txs []*types.Transaction, receipts []*types.Receipt, outcxs []*types.CXReceipt, incxs []*types.CXReceiptsProof, stks []*staking.StakingTransaction) (*types.Block, error) diff --git a/core/blockchain.go b/core/blockchain.go index ac5aadc8f..d6faf5513 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -57,19 +57,19 @@ 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 + validatorMapCacheLimit = 2 // BlockChainVersion ensures that an incompatible database forces a resync from scratch. BlockChainVersion = 3 @@ -122,18 +122,18 @@ 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 + validatorMapCache *lru.Cache // Cache of validator list quit chan struct{} // blockchain quit channel running int32 // running must be called atomically @@ -171,30 +171,30 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par epochCache, _ := lru.New(epochCacheLimit) randomnessCache, _ := lru.New(randomnessCacheLimit) stakingCache, _ := lru.New(stakingCacheLimit) - validatorListCache, _ := lru.New(validatorListCacheLimit) + validatorMapCache, _ := lru.New(validatorMapCacheLimit) 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, + validatorMapCache: validatorMapCache, + engine: engine, + vmConfig: vmConfig, + badBlocks: badBlocks, } bc.SetValidator(NewBlockValidator(chainConfig, bc, engine)) bc.SetProcessor(NewStateProcessor(chainConfig, bc, engine)) @@ -2280,23 +2280,22 @@ func (bc *BlockChain) WriteStakingValidator(v *staking.ValidatorWrapper) error { return nil } -// ReadValidatorList reads the addresses of current all validators -func (bc *BlockChain) ReadValidatorList() ([]common.Address, error) { - if cached, ok := bc.validatorListCache.Get("validatorList"); ok { +// ReadValidatorMap reads the addresses of current all validators +func (bc *BlockChain) ReadValidatorMap() (map[common.Address]struct{}, error) { + if cached, ok := bc.validatorMapCache.Get("validatorMap"); ok { by := cached.([]byte) - list := []common.Address{} - if err := rlp.DecodeBytes(by, &list); err != nil { + m := make(map[common.Address]struct{}) + if err := rlp.DecodeBytes(by, &m); err != nil { return nil, err } - return list, nil + return m, nil } - - return rawdb.ReadValidatorList(bc.db) + return rawdb.ReadValidatorMap(bc.db) } -// WriteValidatorList writes the list of validator addresses to database -func (bc *BlockChain) WriteValidatorList(addrs []common.Address) error { - err := rawdb.WriteValidatorList(bc.db, addrs) +// WriteValidatorMap writes the list of validator addresses to database +func (bc *BlockChain) WriteValidatorMap(addrs map[common.Address]struct{}) error { + err := rawdb.WriteValidatorMap(bc.db, addrs) if err != nil { return err } @@ -2304,7 +2303,33 @@ func (bc *BlockChain) WriteValidatorList(addrs []common.Address) error { if err != nil { return err } - bc.validatorListCache.Add("validatorList", by) + bc.validatorMapCache.Add("validatorMap", by) + return nil +} + +// UpdateValidatorMap updates the validator map according to staking transaction +func (bc *BlockChain) UpdateValidatorMap(tx *staking.StakingTransaction) error { + switch tx.StakingType() { + case staking.DirectiveCreateValidator: + createValidator := tx.StakingMessage().(staking.CreateValidator) + m, err := bc.ReadValidatorMap() + if err != nil { + return err + } + if m == nil { + m = make(map[common.Address]struct{}) + } + m[createValidator.ValidatorAddress] = struct{}{} + err = bc.WriteValidatorMap(m) + return err + + // following cases are placeholder for now + case staking.DirectiveEditValidator: + case staking.DirectiveDelegate: + case staking.DirectiveUndelegate: + case staking.DirectiveCollectRewards: + default: + } return nil } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index eb2e5cf5a..fb0ed780f 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -641,29 +641,29 @@ func WriteStakingValidator(db DatabaseWriter, v *staking.ValidatorWrapper) error return err } -// ReadValidatorList retrieves staking validator by its address -func ReadValidatorList(db DatabaseReader) ([]common.Address, error) { - data, err := db.Get([]byte("validatorList")) +// ReadValidatorMap retrieves staking validator by its address +func ReadValidatorMap(db DatabaseReader) (map[common.Address]struct{}, error) { + data, err := db.Get([]byte("validatorMap")) if len(data) == 0 || err != nil { - utils.Logger().Info().Err(err).Msg("ReadValidatorList") + utils.Logger().Info().Err(err).Msg("ReadValidatorMap") return nil, err } - addrs := []common.Address{} + addrs := make(map[common.Address]struct{}) if err := rlp.DecodeBytes(data, &addrs); err != nil { - utils.Logger().Error().Err(err).Msg("Unable to Decode validator List from database") + utils.Logger().Error().Err(err).Msg("Unable to Decode validator Map from database") return nil, err } return addrs, nil } -// WriteValidatorList stores staking validator's information by its address -func WriteValidatorList(db DatabaseWriter, addrs []common.Address) error { +// WriteValidatorMap stores staking validator's information by its address +func WriteValidatorMap(db DatabaseWriter, addrs map[common.Address]struct{}) error { bytes, err := rlp.EncodeToBytes(addrs) if err != nil { - utils.Logger().Error().Msg("[WriteValidatorList] Failed to encode") + utils.Logger().Error().Msg("[WriteValidatorMap] Failed to encode") } - if err := db.Put([]byte("validatorList"), bytes); err != nil { - utils.Logger().Error().Msg("[WriteValidatorList] Failed to store to database") + if err := db.Put([]byte("validatorMap"), bytes); err != nil { + utils.Logger().Error().Msg("[WriteValidatorMap] Failed to store to database") } return err } diff --git a/core/state/state_object.go b/core/state/state_object.go index 7fd8aefe0..a766c7e76 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -25,6 +25,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" + + "github.com/harmony-one/harmony/staking" ) var emptyCodeHash = crypto.Keccak256(nil) @@ -393,5 +395,10 @@ func (so *Object) Value() *big.Int { } // IsValidator checks whether it is a validator object -func (so *Object) IsValidator() { +func (so *Object) IsValidator(db Database) bool { + value := so.GetState(db, staking.IsValidatorKey) + if value == (common.Hash{}) { + return false + } + return true } diff --git a/core/state/statedb.go b/core/state/statedb.go index b82dea7de..a8a539b50 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -30,6 +30,8 @@ import ( "github.com/ethereum/go-ethereum/trie" "github.com/harmony-one/harmony/core/types" + "github.com/harmony-one/harmony/staking" + stk "github.com/harmony-one/harmony/staking/types" ) type revision struct { @@ -679,3 +681,46 @@ func (db *DB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) { //log.Debug("Trie cache stats after commit", "misses", trie.CacheMisses(), "unloads", trie.CacheUnloads()) return root, err } + +// GetStakingInfo update staking information of a given validator (including delegation info) +func (db *DB) GetStakingInfo(addr common.Address) *stk.ValidatorWrapper { + by := db.GetCode(addr) + if len(by) == 0 { + return nil + } + val := stk.ValidatorWrapper{} + err := rlp.DecodeBytes(by, &val) + if err != nil { + return nil + } + return &val +} + +// UpdateStakingInfo update staking information of a given validator (including delegation info) +func (db *DB) UpdateStakingInfo(addr common.Address, val *stk.ValidatorWrapper) error { + by, err := rlp.EncodeToBytes(val) + if err != nil { + return err + } + db.SetCode(addr, by) + return nil +} + +// SetValidatorFlag checks whether it is a validator object +func (db *DB) SetValidatorFlag(addr common.Address) { + db.SetState(addr, staking.IsValidatorKey, staking.IsValidator) +} + +// UnsetValidatorFlag checks whether it is a validator object +func (db *DB) UnsetValidatorFlag(addr common.Address) { + db.SetState(addr, staking.IsValidatorKey, common.Hash{}) +} + +// IsValidator checks whether it is a validator object +func (db *DB) IsValidator(addr common.Address) bool { + so := db.getStateObject(addr) + if so == nil { + return false + } + return so.IsValidator(db.db) +} diff --git a/core/state_processor.go b/core/state_processor.go index 5a8ee27a2..744086350 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -18,6 +18,7 @@ package core import ( "fmt" + "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -29,6 +30,7 @@ import ( "github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/internal/params" "github.com/harmony-one/harmony/internal/utils" + staking "github.com/harmony-one/harmony/staking/types" ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -179,6 +181,49 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo return receipt, cxReceipt, gas, err } +// ApplyStakingTransaction attempts to apply a staking transaction to the given state database +// and uses the input parameters for its environment. It returns the receipt +// for the staking transaction, gas used and an error if the transaction failed, +// indicating the block was invalid. +// staking transaction will use the code field in the account to store the staking information +func ApplyStakingTransaction( + config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.DB, + header *block.Header, tx *staking.StakingTransaction, usedGas *uint64, cfg vm.Config) (receipt *types.Receipt, gas uint64, err error) { + + msg, err := StakingToMessage(tx, header.Number()) + if err != nil { + return nil, 0, err + } + + // Create a new context to be used in the EVM environment + context := NewEVMContext(msg, header, bc, author) + + // Create a new environment which holds all relevant information + // about the transaction and calling mechanisms. + vmenv := vm.NewEVM(context, statedb, config, cfg) + // Apply the transaction to the current state (included in the env) + gas, err = ApplyStakingMessage(vmenv, msg, gp) + + // even there is error, we charge it + if err != nil { + return nil, gas, err + } + + // Update the state with pending changes + var root []byte + if config.IsS3(header.Epoch()) { + statedb.Finalise(true) + } else { + root = statedb.IntermediateRoot(config.IsS3(header.Epoch())).Bytes() + } + *usedGas += gas + receipt = types.NewReceipt(root, false, *usedGas) + receipt.TxHash = tx.Hash() + receipt.GasUsed = gas + + return receipt, gas, nil +} + // ApplyIncomingReceipt will add amount into ToAddress in the receipt func ApplyIncomingReceipt(config *params.ChainConfig, db *state.DB, header *block.Header, cxp *types.CXReceiptsProof) error { if cxp == nil { @@ -199,3 +244,24 @@ func ApplyIncomingReceipt(config *params.ChainConfig, db *state.DB, header *bloc } return nil } + +// StakingToMessage returns the staking transaction as a core.Message. +// requires a signer to derive the sender. +// put it here to avoid cyclic import +func StakingToMessage(tx *staking.StakingTransaction, blockNum *big.Int) (types.Message, error) { + payload, err := tx.StakingMsgToBytes() + if err != nil { + return types.Message{}, err + } + from, err := tx.SenderAddress() + if err != nil { + return types.Message{}, err + } + msg := types.NewStakingMessage(from, tx.Nonce(), tx.Gas(), tx.Price(), payload, blockNum) + stkType := tx.StakingType() + if _, ok := types.StakingTypeMap[stkType]; !ok { + return types.Message{}, staking.ErrInvalidStakingKind + } + msg.SetType(types.StakingTypeMap[stkType]) + return msg, nil +} diff --git a/core/state_transition.go b/core/state_transition.go index a694dacdf..81aa55a65 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -22,14 +22,18 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/vm" "github.com/harmony-one/harmony/internal/params" "github.com/harmony-one/harmony/internal/utils" + staking "github.com/harmony-one/harmony/staking/types" ) var ( errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas") + errValidatorExist = errors.New("staking validator address already exists") + errValidatorNotExist = errors.New("staking validator address does not exist") ) /* @@ -75,6 +79,7 @@ type Message interface { CheckNonce() bool Data() []byte Type() types.TransactionType + BlockNum() *big.Int } // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. @@ -134,6 +139,11 @@ func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, return NewStateTransition(evm, msg, gp).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() +} + // to returns the recipient of the message. func (st *StateTransition) to() common.Address { if st.msg == nil || st.msg.To() == nil /* contract creation */ { @@ -252,3 +262,105 @@ func (st *StateTransition) refundGas() { func (st *StateTransition) gasUsed() uint64 { return st.initialGas - st.gas } + +// StakingTransitionDb will transition the state by applying the staking message and +// returning the result including the used gas. It returns an error if failed. +// It is used for staking transaction only +func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) { + if err = st.preCheck(); err != nil { + return 0, err + } + msg := st.msg + sender := vm.AccountRef(msg.From()) + homestead := st.evm.ChainConfig().IsS3(st.evm.EpochNumber) // s3 includes homestead + + // Pay intrinsic gas + // TODO: add new formula for staking transaction + gas, err := IntrinsicGas(st.data, false, homestead) + if err != nil { + return 0, err + } + if err = st.useGas(gas); err != nil { + return 0, err + } + + // Increment the nonce for the next transaction + st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) + + switch msg.Type() { + case types.StakeNewVal: + stkMsg := &staking.CreateValidator{} + if err = rlp.DecodeBytes(msg.Data(), stkMsg); err != nil { + break + } + err = st.applyCreateValidatorTx(stkMsg, msg.BlockNum()) + + case types.StakeEditVal: + stkMsg := &staking.EditValidator{} + if err = rlp.DecodeBytes(msg.Data(), stkMsg); err != nil { + break + } + err = st.applyEditValidatorTx(stkMsg, msg.BlockNum()) + + case types.Delegate: + stkMsg := &staking.Delegate{} + if err = rlp.DecodeBytes(msg.Data(), stkMsg); err != nil { + break + } + err = st.applyDelegateTx(stkMsg) + + case types.Undelegate: + stkMsg := &staking.Undelegate{} + if err = rlp.DecodeBytes(msg.Data(), stkMsg); err != nil { + break + } + err = st.applyUndelegateTx(stkMsg) + case types.CollectRewards: + + default: + return 0, staking.ErrInvalidStakingKind + } + st.refundGas() + + return st.gasUsed(), err +} + +func (st *StateTransition) applyCreateValidatorTx(nv *staking.CreateValidator, blockNum *big.Int) error { + if st.state.IsValidator(nv.ValidatorAddress) { + return errValidatorExist + } + v, err := staking.CreateValidatorFromNewMsg(nv) + if err != nil { + return err + } + v.UpdateHeight = blockNum + wrapper := staking.ValidatorWrapper{*v, nil, nil, nil} + if err := st.state.UpdateStakingInfo(v.Address, &wrapper); err != nil { + return err + } + st.state.SetValidatorFlag(v.Address) + return nil +} + +func (st *StateTransition) applyEditValidatorTx(ev *staking.EditValidator, blockNum *big.Int) error { + if !st.state.IsValidator(ev.ValidatorAddress) { + return errValidatorNotExist + } + wrapper := st.state.GetStakingInfo(ev.ValidatorAddress) + if err := staking.UpdateValidatorFromEditMsg(&wrapper.Validator, ev); err != nil { + return err + } + wrapper.Validator.UpdateHeight = blockNum + if err := st.state.UpdateStakingInfo(ev.ValidatorAddress, wrapper); err != nil { + return err + } + return nil +} + +func (st *StateTransition) applyDelegateTx(delegate *staking.Delegate) error { + return nil +} + +func (st *StateTransition) applyUndelegateTx(undelegate *staking.Undelegate) error { + return nil +} diff --git a/core/types/transaction.go b/core/types/transaction.go index ea00ba031..22df976a0 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -31,6 +31,7 @@ import ( "github.com/harmony-one/harmony/crypto/hash" common2 "github.com/harmony-one/harmony/internal/common" + staking "github.com/harmony-one/harmony/staking/types" ) // no go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go @@ -48,8 +49,18 @@ const ( SameShardTx TransactionType = iota SubtractionOnly // only subtract tokens from source shard account InvalidTx + StakeNewVal + StakeEditVal + Delegate + Undelegate + CollectRewards ) +// StakingTypeMap is the map from staking type to transactionType +var StakingTypeMap = map[staking.Directive]TransactionType{staking.DirectiveCreateValidator: StakeNewVal, + staking.DirectiveEditValidator: StakeEditVal, staking.DirectiveDelegate: Delegate, + staking.DirectiveUndelegate: Undelegate} + // Transaction struct. type Transaction struct { data txdata @@ -67,6 +78,16 @@ func (txType TransactionType) String() string { return "SubtractionOnly" } else if txType == InvalidTx { return "InvalidTx" + } else if txType == StakeNewVal { + return "StakeNewValidator" + } else if txType == StakeEditVal { + return "StakeEditValidator" + } else if txType == Delegate { + return "Delegate" + } else if txType == Undelegate { + return "Undelegate" + } else if txType == CollectRewards { + return "CollectRewards" } return "Unknown" } @@ -541,6 +562,7 @@ type Message struct { gasPrice *big.Int data []byte checkNonce bool + blockNum *big.Int txType TransactionType } @@ -558,6 +580,20 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b } } +// NewStakingMessage returns new message of staking type +// always need checkNonce +func NewStakingMessage(from common.Address, nonce uint64, gasLimit uint64, gasPrice *big.Int, data []byte, blockNum *big.Int) Message { + return Message{ + from: from, + nonce: nonce, + gasLimit: gasLimit, + gasPrice: new(big.Int).Set(gasPrice), + data: data, + checkNonce: true, + blockNum: blockNum, + } +} + // From returns from address from Message. func (m Message) From() common.Address { return m.from @@ -608,6 +644,11 @@ func (m Message) SetType(typ TransactionType) { m.txType = typ } +// BlockNum returns the blockNum of the tx belongs to +func (m Message) BlockNum() *big.Int { + return m.blockNum +} + // RecentTxsStats is a recent transactions stats map tracking stats like BlockTxsCounts. type RecentTxsStats map[uint64]BlockTxsCounts diff --git a/core/vm/interface.go b/core/vm/interface.go index 8abb41380..8a90a29f6 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/harmony-one/harmony/core/types" + staking "github.com/harmony-one/harmony/staking/types" ) // StateDB is an EVM database for full state querying. @@ -39,6 +40,12 @@ type StateDB interface { SetCode(common.Address, []byte) GetCodeSize(common.Address) int + GetStakingInfo(common.Address) *staking.ValidatorWrapper + UpdateStakingInfo(common.Address, *staking.ValidatorWrapper) error + SetValidatorFlag(common.Address) + UnsetValidatorFlag(common.Address) + IsValidator(common.Address) bool + AddRefund(uint64) SubRefund(uint64) GetRefund() uint64 diff --git a/node/node.go b/node/node.go index 86ba5c58e..782089b8e 100644 --- a/node/node.go +++ b/node/node.go @@ -349,15 +349,11 @@ func (node *Node) AddPendingReceipts(receipts *types.CXReceiptsProof) { // Take out a subset of valid transactions from the pending transaction list // Note the pending transaction list will then contain the rest of the txs -func (node *Node) getTransactionsForNewBlock( - coinbase common.Address, -) types.Transactions { - +func (node *Node) getTransactionsForNewBlock(coinbase common.Address) (types.Transactions, staking.StakingTransactions) { txsThrottleConfig := core.ShardingSchedule.TxsThrottleConfig() // the next block number to be added in consensus protocol, which is always one more than current chain header block newBlockNum := node.Blockchain().CurrentBlock().NumberU64() + 1 - // remove old (> txsThrottleConfigRecentTxDuration) blockNum keys from recentTxsStats and initiailize for the new block for blockNum := range node.recentTxsStats { recentTxsBlockNumGap := uint64(txsThrottleConfig.RecentTxDuration / node.BlockPeriod) @@ -366,23 +362,20 @@ func (node *Node) getTransactionsForNewBlock( } } node.recentTxsStats[newBlockNum] = make(types.BlockTxsCounts) - // Must update to the correct current state before processing potential txns if err := node.Worker.UpdateCurrent(coinbase); err != nil { utils.Logger().Error(). Err(err). Msg("Failed updating worker's state before txn selection") - return types.Transactions{} + return types.Transactions{}, staking.StakingTransactions{} } node.pendingTxMutex.Lock() defer node.pendingTxMutex.Unlock() node.pendingStakingTxMutex.Lock() defer node.pendingStakingTxMutex.Unlock() - pendingTransactions := types.Transactions{} pendingStakingTransactions := staking.StakingTransactions{} - for _, tx := range node.pendingTransactions { pendingTransactions = append(pendingTransactions, tx) } @@ -392,6 +385,9 @@ func (node *Node) getTransactionsForNewBlock( selected, unselected, invalid := node.Worker.SelectTransactionsForNewBlock(newBlockNum, pendingTransactions, node.recentTxsStats, txsThrottleConfig, coinbase) + selectedStaking, unselectedStaking, invalidStaking := + node.Worker.SelectStakingTransactionsForNewBlock(newBlockNum, pendingStakingTransactions, coinbase) + node.pendingTransactions = make(map[common.Hash]*types.Transaction) for _, unselectedTx := range unselected { node.pendingTransactions[unselectedTx.Hash()] = unselectedTx @@ -402,7 +398,17 @@ func (node *Node) getTransactionsForNewBlock( Int("invalidDiscarded", len(invalid)). Msg("Selecting Transactions") - return selected + node.pendingStakingTransactions = make(map[common.Hash]*staking.StakingTransaction) + for _, unselectedStakingTx := range unselectedStaking { + node.pendingStakingTransactions[unselectedStakingTx.Hash()] = unselectedStakingTx + } + utils.Logger().Info(). + Int("remainPending", len(node.pendingStakingTransactions)). + Int("selected", len(unselectedStaking)). + Int("invalidDiscarded", len(invalidStaking)). + Msg("Selecting Transactions") + + return selected, selectedStaking } func (node *Node) startRxPipeline( diff --git a/node/node_handler_test.go b/node/node_handler_test.go index 84a5d0703..49a012270 100644 --- a/node/node_handler_test.go +++ b/node/node_handler_test.go @@ -33,8 +33,8 @@ func TestAddNewBlock(t *testing.T) { nodeconfig.SetNetworkType(nodeconfig.Devnet) node := New(host, consensus, testDBFactory, false) - selectedTxs := node.getTransactionsForNewBlock(common.Address{}) - node.Worker.CommitTransactions(selectedTxs, common.Address{}) + selectedTxs, stks := node.getTransactionsForNewBlock(common.Address{}) + node.Worker.CommitTransactions(selectedTxs, stks, common.Address{}) block, _ := node.Worker.FinalizeNewBlock([]byte{}, []byte{}, 0, common.Address{}, nil, nil) err = node.AddNewBlock(block) @@ -65,8 +65,8 @@ func TestVerifyNewBlock(t *testing.T) { } node := New(host, consensus, testDBFactory, false) - selectedTxs := node.getTransactionsForNewBlock(common.Address{}) - node.Worker.CommitTransactions(selectedTxs, common.Address{}) + selectedTxs, stks := node.getTransactionsForNewBlock(common.Address{}) + node.Worker.CommitTransactions(selectedTxs, stks, common.Address{}) block, _ := node.Worker.FinalizeNewBlock([]byte{}, []byte{}, 0, common.Address{}, nil, nil) if err := node.VerifyNewBlock(block); err != nil { diff --git a/node/node_newblock.go b/node/node_newblock.go index dd7e1f81e..711cb7225 100644 --- a/node/node_newblock.go +++ b/node/node_newblock.go @@ -81,9 +81,9 @@ func (node *Node) proposeNewBlock() (*types.Block, error) { coinbase := node.Consensus.SelfAddress // Prepare transactions including staking transactions - selectedTxs := node.getTransactionsForNewBlock(coinbase) + selectedTxs, selectedStakingTxs := node.getTransactionsForNewBlock(coinbase) - if err := node.Worker.CommitTransactions(selectedTxs, coinbase); err != nil { + if err := node.Worker.CommitTransactions(selectedTxs, selectedStakingTxs, coinbase); err != nil { ctxerror.Log15(utils.GetLogger().Error, ctxerror.New("cannot commit transactions"). WithCause(err)) diff --git a/node/worker/worker.go b/node/worker/worker.go index 4d53fe3bf..a72f1c29d 100644 --- a/node/worker/worker.go +++ b/node/worker/worker.go @@ -13,6 +13,7 @@ import ( "github.com/harmony-one/harmony/core" "github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/types" + "github.com/harmony-one/harmony/core/values" "github.com/harmony-one/harmony/core/vm" shardingconfig "github.com/harmony-one/harmony/internal/configs/sharding" "github.com/harmony-one/harmony/internal/ctxerror" @@ -144,6 +145,71 @@ func (w *Worker) SelectTransactionsForNewBlock(newBlockNum uint64, txs types.Tra return selected, unselected, invalid } +// SelectStakingTransactionsForNewBlock selects staking transactions for new block. +func (w *Worker) SelectStakingTransactionsForNewBlock( + newBlockNum uint64, txs staking.StakingTransactions, + coinbase common.Address) (staking.StakingTransactions, staking.StakingTransactions, staking.StakingTransactions) { + + // only beaconchain process staking transaction + if w.chain.ShardID() != values.BeaconChainShardID { + return nil, nil, nil + } + + // staking transaction share the same gasPool with normal transactions + if w.current.gasPool == nil { + w.current.gasPool = new(core.GasPool).AddGas(w.current.header.GasLimit()) + } + + selected := staking.StakingTransactions{} + unselected := staking.StakingTransactions{} + invalid := staking.StakingTransactions{} + for _, tx := range txs { + snap := w.current.state.Snapshot() + _, err := w.commitStakingTransaction(tx, coinbase) + if err != nil { + w.current.state.RevertToSnapshot(snap) + invalid = append(invalid, tx) + utils.Logger().Error().Err(err).Str("stakingTxId", tx.Hash().Hex()).Msg("Commit staking transaction error") + } else { + selected = append(selected, tx) + utils.Logger().Info().Str("stakingTxId", tx.Hash().Hex()).Uint64("txGasLimit", tx.Gas()).Msg("StakingTransaction gas limit info") + } + } + + utils.Logger().Info().Uint64("newBlockNum", newBlockNum).Uint64("blockGasLimit", + w.current.header.GasLimit()).Uint64("blockGasUsed", + w.current.header.GasUsed()).Msg("[SelectStakingTransaction] Block gas limit and usage info") + + return selected, unselected, invalid + +} + +func (w *Worker) commitStakingTransaction(tx *staking.StakingTransaction, coinbase common.Address) ([]*types.Log, error) { + snap := w.current.state.Snapshot() + gasUsed := w.current.header.GasUsed() + receipt, _, err := + core.ApplyStakingTransaction(w.config, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &gasUsed, vm.Config{}) + w.current.header.SetGasUsed(gasUsed) + if err != nil { + w.current.state.RevertToSnapshot(snap) + return nil, err + } + if receipt == nil { + return nil, fmt.Errorf("nil staking receipt") + } + + err = w.chain.UpdateValidatorMap(tx) + // keep offchain database consistency with onchain we need revert + // but it should not happend unless local database corrupted + if err != nil { + w.current.state.RevertToSnapshot(snap) + return nil, err + } + w.current.stkingTxs = append(w.current.stkingTxs, tx) + w.current.receipts = append(w.current.receipts, receipt) + return receipt.Logs, nil +} + func (w *Worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { snap := w.current.state.Snapshot() @@ -152,6 +218,7 @@ func (w *Worker) commitTransaction(tx *types.Transaction, coinbase common.Addres w.current.header.SetGasUsed(gasUsed) if err != nil { w.current.state.RevertToSnapshot(snap) + utils.Logger().Error().Err(err).Str("stakingTxId", tx.Hash().Hex()).Msg("Offchain ValidatorMap Read/Write Error") return nil, err } if receipt == nil { @@ -167,9 +234,8 @@ func (w *Worker) commitTransaction(tx *types.Transaction, coinbase common.Addres return receipt.Logs, nil } -// CommitTransactions commits transactions including staking transactions. -func (w *Worker) CommitTransactions( - txs types.Transactions, coinbase common.Address) error { +// CommitTransactions commits transactions. +func (w *Worker) CommitTransactions(txs types.Transactions, stakingTxns staking.StakingTransactions, coinbase common.Address) error { // Must update to the correct current state before processing potential txns if err := w.UpdateCurrent(coinbase); err != nil { utils.Logger().Error(). @@ -190,6 +256,10 @@ func (w *Worker) CommitTransactions( } } + for _, stakingTx := range stakingTxns { + _ = stakingTx + // TODO: add logic to commit staking txns + } return nil } @@ -331,40 +401,16 @@ func (w *Worker) FinalizeNewBlock(sig []byte, signers []byte, viewID uint64, coi } } - stks, _, err := w.UpdateStakeInformation() - s := w.current.state.Copy() copyHeader := types.CopyHeader(w.current.header) - block, err := w.engine.Finalize( - w.chain, copyHeader, s, w.current.txs, w.current.receipts, w.current.outcxs, w.current.incxs, - stks, - ) + block, err := w.engine.Finalize(w.chain, copyHeader, s, w.current.txs, w.current.receipts, w.current.outcxs, w.current.incxs, w.current.stkingTxs) if err != nil { return nil, ctxerror.New("cannot finalize block").WithCause(err) } return block, nil } -// UpdateStakeInformation updates validator and its delegation information -func (w *Worker) UpdateStakeInformation() ([]*staking.StakingTransaction, []*staking.ValidatorWrapper, error) { - // node.pendingStakingTransactions = make(map[common.Hash]*staking.StakingTransaction) - // for _, unselectedStakingTx := range unselectedStaking { - // node.pendingStakingTransactions[unselectedStakingTx.Hash()] = unselectedStakingTx - // } - - addrs, err := w.chain.ReadValidatorList() - if err != nil { - return nil, nil, err - } - //validatorInfo := []staking.ValidatorWrapper{} - for _, addr := range addrs { - _ = addr - } - - return nil, nil, nil -} - // New create a new worker object. func New(config *params.ChainConfig, chain *core.BlockChain, engine consensus_engine.Engine) *Worker { worker := &Worker{ diff --git a/node/worker/worker_test.go b/node/worker/worker_test.go index 01cb78104..c52ee4cb5 100644 --- a/node/worker/worker_test.go +++ b/node/worker/worker_test.go @@ -74,7 +74,7 @@ func TestCommitTransactions(t *testing.T) { tx, _ := types.SignTx(types.NewTransaction(baseNonce, testBankAddress, uint32(0), big.NewInt(int64(denominations.One*randAmount)), params.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey) // Commit the tx to the worker - err := worker.CommitTransactions(types.Transactions{tx}, testBankAddress) + err := worker.CommitTransactions(types.Transactions{tx}, nil, testBankAddress) if err != nil { t.Error(err) } diff --git a/staking/params.go b/staking/params.go new file mode 100644 index 000000000..7c3ce9e57 --- /dev/null +++ b/staking/params.go @@ -0,0 +1,18 @@ +package staking + +import ( + "github.com/ethereum/go-ethereum/crypto" +) + +const ( + isValidatorKeyStr = "Harmony/IsValidator/v0" + isValidatorStr = "Harmony/IsAValidator/v0" + isNotValidatorStr = "Harmony/IsNotAValidator/v0" +) + +// keys used to retrieve staking related informatio +var ( + IsValidatorKey = crypto.Keccak256Hash([]byte(isValidatorKeyStr)) + IsValidator = crypto.Keccak256Hash([]byte(isValidatorStr)) + IsNotValidator = crypto.Keccak256Hash([]byte(isNotValidatorStr)) +) diff --git a/staking/types/delegation.go b/staking/types/delegation.go index 35c55acc0..a6682d4fb 100644 --- a/staking/types/delegation.go +++ b/staking/types/delegation.go @@ -1,249 +1,72 @@ package types import ( - "fmt" + "errors" "math/big" - "strings" - "github.com/ethereum/go-ethereum/rlp" "github.com/harmony-one/harmony/internal/common" ) -// DVPair is struct that just has a delegator-validator pair with no other data. -// It is intended to be used as a marshalable pointer. For example, a DVPair can be used to construct the -// key to getting an UnbondingDelegation from state. -type DVPair struct { - DelegatorAddress common.Address - ValidatorAddress common.Address -} - -// DVVTriplet is struct that just has a delegator-validator-validator triplet with no other data. -// It is intended to be used as a marshalable pointer. For example, a DVVTriplet can be used to construct the -// key to getting a Redelegation from state. -type DVVTriplet struct { - DelegatorAddress common.Address - ValidatorSrcAddress common.Address - ValidatorDstAddress common.Address -} +var ( + errInsufficientBalance = errors.New("Insufficient balance to undelegate") + errInvalidAmount = errors.New("Invalid amount, must be positive") +) // Delegation represents the bond with tokens held by an account. It is // owned by one delegator, and is associated with the voting power of one // validator. type Delegation struct { - DelegatorAddress common.Address `json:"delegator_address" yaml:"delegator_address"` - ValidatorAddress common.Address `json:"validator_address" yaml:"validator_address"` - Amount *big.Int `json:"amount" yaml:"amount"` + DelegatorAddress common.Address `json:"delegator_address" yaml:"delegator_address"` + Amount *big.Int `json:"amount" yaml:"amount"` + Entries []*UndelegationEntry `json:"entries" yaml:"entries"` +} + +// UndelegationEntry represents one undelegation entry +type UndelegationEntry struct { + Amount *big.Int + Epoch *big.Int } // NewDelegation creates a new delegation object -func NewDelegation(delegatorAddr common.Address, validatorAddr common.Address, +func NewDelegation(delegatorAddr common.Address, amount *big.Int) Delegation { - return Delegation{ DelegatorAddress: delegatorAddr, - ValidatorAddress: validatorAddr, Amount: amount, } } -// MarshalDelegation return the delegation -func MarshalDelegation(delegation Delegation) ([]byte, error) { - return rlp.EncodeToBytes(delegation) -} - -// UnmarshalDelegation return the delegation -func UnmarshalDelegation(by []byte) (*Delegation, error) { - decoded := &Delegation{} - err := rlp.DecodeBytes(by, decoded) - return decoded, err -} - -// GetDelegatorAddr returns DelegatorAddr -func (d Delegation) GetDelegatorAddr() common.Address { return d.DelegatorAddress } - -// GetValidatorAddr returns ValidatorAddr -func (d Delegation) GetValidatorAddr() common.Address { return d.ValidatorAddress } - -// GetAmount returns amount of a delegation -func (d Delegation) GetAmount() *big.Int { return d.Amount } - -// String returns a human readable string representation of a Delegation. -func (d Delegation) String() string { - return fmt.Sprintf(` -Delegation: -Delegator: %s -Validator: %s -Amount: %s -`, d.DelegatorAddress, d.ValidatorAddress, d.Amount) -} - -// Delegations is a collection of delegations -type Delegations []Delegation - -// String returns the string representation of a list of delegations -func (d Delegations) String() (out string) { - for _, del := range d { - out += del.String() + "\n" - } - return strings.TrimSpace(out) -} - -// UnbondingDelegation stores all of a single delegator's unbonding bonds -// for a single validator in an time-ordered list -type UnbondingDelegation struct { - DelegatorAddress common.Address `json:"delegator_address" yaml:"delegator_address"` // delegator - ValidatorAddress common.Address `json:"validator_address" yaml:"validator_address"` // validator unbonding from operator addr - Entries []UnbondingDelegationEntry `json:"entries" yaml:"entries"` // unbonding delegation entries -} - -// UnbondingDelegationEntry - entry to an UnbondingDelegation -type UnbondingDelegationEntry struct { - ExitEpoch *big.Int `json:"exit_epoch" yaml:"exit_epoch"` // epoch which the unbonding begins - Amount *big.Int `json:"amount" yaml:"amount"` // atoms to receive at completion -} - -// NewUnbondingDelegation - create a new unbonding delegation object -func NewUnbondingDelegation(delegatorAddr common.Address, - validatorAddr common.Address, epoch *big.Int, amt *big.Int) UnbondingDelegation { - - entry := NewUnbondingDelegationEntry(epoch, amt) - return UnbondingDelegation{ - DelegatorAddress: delegatorAddr, - ValidatorAddress: validatorAddr, - Entries: []UnbondingDelegationEntry{entry}, +// AddEntry - append entry to the undelegation +func (d *Delegation) AddEntry(epoch *big.Int, amt *big.Int) error { + if d.Amount.Cmp(amt) < 0 { + return errInsufficientBalance } -} - -// NewUnbondingDelegationEntry - create a new unbonding delegation object -func NewUnbondingDelegationEntry(epoch *big.Int, amt *big.Int) UnbondingDelegationEntry { - return UnbondingDelegationEntry{ - ExitEpoch: epoch, - Amount: amt, + if amt.Sign() <= 0 { + return errInvalidAmount } -} + d.Amount.Sub(d.Amount, amt) -// AddEntry - append entry to the unbonding delegation -// if there exists same ExitEpoch entry, merge the amount -// TODO: check the total amount not exceed the staking amount call this function -func (d *UnbondingDelegation) AddEntry(epoch *big.Int, amt *big.Int) { - entry := NewUnbondingDelegationEntry(epoch, amt) - for i := range d.Entries { - if d.Entries[i].ExitEpoch == entry.ExitEpoch { - d.Entries[i].Amount.Add(d.Entries[i].Amount, entry.Amount) - return + for _, entry := range d.Entries { + if entry.Epoch.Cmp(epoch) == 0 { + entry.Amount.Add(entry.Amount, amt) + return nil } } - // same exit epoch entry not found - d.Entries = append(d.Entries, entry) - return + item := UndelegationEntry{amt, epoch} + d.Entries = append(d.Entries, &item) + return nil } -// String returns a human readable string representation of an UnbondingDelegation. -func (d UnbondingDelegation) String() string { - out := fmt.Sprintf(`Unbonding Delegations between: - Delegator: %s - Validator: %s - Entries:`, d.DelegatorAddress, d.ValidatorAddress) +// DeleteEntry - delete an entry from the undelegation +// Opimize it +func (d *Delegation) DeleteEntry(epoch *big.Int) { + entries := []*UndelegationEntry{} for i, entry := range d.Entries { - out += fmt.Sprintf(` Unbonding Delegation %d: - ExitEpoch: %v - Amount: %s`, i, entry.ExitEpoch, entry.Amount) - } - return out -} - -// UnbondingDelegations is a collection of UnbondingDelegation -type UnbondingDelegations []UnbondingDelegation - -func (ubds UnbondingDelegations) String() (out string) { - for _, u := range ubds { - out += u.String() + "\n" - } - return strings.TrimSpace(out) -} - -// Redelegation contains the list of a particular delegator's -// redelegating bonds from a particular source validator to a -// particular destination validator -type Redelegation struct { - DelegatorAddress common.Address `json:"delegator_address" yaml:"delegator_address"` // delegator - ValidatorSrcAddress common.Address `json:"validator_src_address" yaml:"validator_src_address"` // validator redelegation source operator addr - ValidatorDstAddress common.Address `json:"validator_dst_address" yaml:"validator_dst_address"` // validator redelegation destination operator addr - Entries []RedelegationEntry `json:"entries" yaml:"entries"` // redelegation entries -} - -// RedelegationEntry - entry to a Redelegation -type RedelegationEntry struct { - Epoch *big.Int `json:"epoch" yaml:"epoch"` // epoch at which the redelegation took place - Amount *big.Int `json:"amount" yaml:"amount"` // amount of destination-validator tokens created by redelegation -} - -// NewRedelegation - create a new redelegation object -func NewRedelegation(delegatorAddr common.Address, validatorSrcAddr, - validatorDstAddr common.Address, epoch *big.Int, amt *big.Int) Redelegation { - entry := NewRedelegationEntry(epoch, amt) - return Redelegation{ - DelegatorAddress: delegatorAddr, - ValidatorSrcAddress: validatorSrcAddr, - ValidatorDstAddress: validatorDstAddr, - Entries: []RedelegationEntry{entry}, - } -} - -// NewRedelegationEntry - create a new redelegation object -func NewRedelegationEntry(epoch *big.Int, amt *big.Int) RedelegationEntry { - return RedelegationEntry{ - Epoch: epoch, - Amount: amt, - } -} - -// AddEntry - append entry to the unbonding delegation -// Merge if has same epoch field -func (d *Redelegation) AddEntry(epoch *big.Int, amt *big.Int) { - entry := NewRedelegationEntry(epoch, amt) - - for i := range d.Entries { - if d.Entries[i].Epoch == entry.Epoch { - d.Entries[i].Amount.Add(d.Entries[i].Amount, entry.Amount) - return + if entry.Epoch.Cmp(epoch) == 0 { + entries = append(d.Entries[:i], d.Entries[i+1:]...) } } - // same epoch entry not found - d.Entries = append(d.Entries, entry) - return -} - -// String returns a human readable string representation of a Redelegation. -func (d Redelegation) String() string { - out := fmt.Sprintf(` -Redelegations between: -Delegator: %s -Source Validator: %s -Destination Validator: %s -Entries: -`, - d.DelegatorAddress, d.ValidatorSrcAddress, d.ValidatorDstAddress, - ) - - for i, entry := range d.Entries { - out += fmt.Sprintf(` Redelegation Entry #%d: - Epoch: %v - Amount: %v -`, - i, entry.Epoch, entry.Amount, - ) - } - - return strings.TrimRight(out, "\n") -} - -// Redelegations are a collection of Redelegation -type Redelegations []Redelegation - -func (d Redelegations) String() (out string) { - for _, red := range d { - out += red.String() + "\n" + if entries != nil { + d.Entries = entries } - return strings.TrimSpace(out) } diff --git a/staking/types/messages.go b/staking/types/messages.go index 5f624c76a..cad3ee704 100644 --- a/staking/types/messages.go +++ b/staking/types/messages.go @@ -4,11 +4,10 @@ import ( "fmt" "math/big" - "github.com/harmony-one/harmony/shard" - "github.com/ethereum/go-ethereum/common" "github.com/harmony-one/harmony/numeric" + "github.com/harmony-one/harmony/shard" "github.com/pkg/errors" ) @@ -52,7 +51,7 @@ func (d Directive) String() string { // CreateValidator - type for creating a new validator type CreateValidator struct { - Description `json:"description" yaml:"description"` + Description *Description `json:"description" yaml:"description"` CommissionRates `json:"commission" yaml:"commission"` MinSelfDelegation *big.Int `json:"min_self_delegation" yaml:"min_self_delegation"` MaxTotalDelegation *big.Int `json:"max_total_delegation" yaml:"max_total_delegation"` diff --git a/staking/types/validator.go b/staking/types/validator.go index 9f4aa9971..aee4743c4 100644 --- a/staking/types/validator.go +++ b/staking/types/validator.go @@ -1,14 +1,15 @@ package types import ( + "errors" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" - "github.com/harmony-one/bls/ffi/go/bls" "github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/numeric" + "github.com/harmony-one/harmony/shard" ) // Define validator staking related const @@ -20,10 +21,16 @@ const ( MaxDetailsLength = 280 ) +var ( + errAddressNotMatch = errors.New("Validator key not match") +) + // ValidatorWrapper contains validator and its delegation information type ValidatorWrapper struct { - Validator `json:"validator" yaml:"validator"` - Delegations []Delegation `json:"delegations" yaml:"delegations"` + Validator `json:"validator" yaml:"validator"` + Delegations []Delegation `json:"delegations" yaml:"delegations"` + SnapshotValidator *Validator `json:"snapshot_validator" yaml:"snaphost_validator"` + SnapshotDelegations []Delegation `json:"snapshot_delegations" yaml:"snapshot_delegations"` } // Validator - data fields for a validator @@ -31,7 +38,7 @@ type Validator struct { // ECDSA address of the validator Address common.Address `json:"address" yaml:"address"` // The BLS public key of the validator for consensus - ValidatingPubKey bls.PublicKey `json:"validating_pub_key" yaml:"validating_pub_key"` + SlotPubKeys []shard.BlsPublicKey `json:"validating_pub_key" yaml:"validating_pub_key"` // The stake put by the validator itself Stake *big.Int `json:"stake" yaml:"stake"` // if unbonding, height at which this validator has begun unbonding @@ -80,7 +87,7 @@ func NewDescription(name, identity, website, securityContact, details string) De // UpdateDescription updates the fields of a given description. An error is // returned if the resulting description contains an invalid length. -func (d Description) UpdateDescription(d2 Description) (Description, error) { +func UpdateDescription(d2 *Description) (Description, error) { return NewDescription( d2.Name, d2.Identity, @@ -125,3 +132,40 @@ func (v *Validator) GetCommissionRate() numeric.Dec { return v.Commission.Rate } // GetMinSelfDelegation returns the minimum amount the validator must stake func (v *Validator) GetMinSelfDelegation() *big.Int { return v.MinSelfDelegation } + +// CreateValidatorFromNewMsg creates validator from NewValidator message +func CreateValidatorFromNewMsg(val *CreateValidator) (*Validator, error) { + desc, err := UpdateDescription(val.Description) + if err != nil { + return nil, err + } + commission := Commission{val.CommissionRates, new(big.Int)} + pubKeys := []shard.BlsPublicKey{} + pubKeys = append(pubKeys, val.SlotPubKeys...) + v := Validator{val.ValidatorAddress, pubKeys, + val.Amount, new(big.Int), val.MinSelfDelegation, false, + commission, desc} + return &v, nil +} + +// UpdateValidatorFromEditMsg updates validator from EditValidator message +func UpdateValidatorFromEditMsg(validator *Validator, edit *EditValidator) error { + if validator.Address != edit.ValidatorAddress { + return errAddressNotMatch + } + desc, err := UpdateDescription(edit.Description) + if err != nil { + return err + } + + validator.Description = desc + + if edit.CommissionRate != nil { + validator.Rate = *edit.CommissionRate + } + + if edit.MinSelfDelegation != nil { + validator.MinSelfDelegation = edit.MinSelfDelegation + } + return nil +} diff --git a/staking/types/validator_test.go b/staking/types/validator_test.go index a68fec372..55ebf5d04 100644 --- a/staking/types/validator_test.go +++ b/staking/types/validator_test.go @@ -4,8 +4,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - - "github.com/harmony-one/harmony/crypto/bls" "github.com/harmony-one/harmony/numeric" ) @@ -13,7 +11,7 @@ func CreateNewValidator() Validator { cr := CommissionRates{Rate: numeric.OneDec(), MaxRate: numeric.OneDec(), MaxChangeRate: numeric.ZeroDec()} c := Commission{cr, big.NewInt(300)} d := Description{Name: "SuperHero", Identity: "YouWillNotKnow", Website: "under_construction", Details: "N/A"} - v := Validator{Address: common.Address{}, ValidatingPubKey: *bls.RandPrivateKey().GetPublicKey(), + v := Validator{Address: common.Address{}, SlotPubKeys: nil, Stake: big.NewInt(500), UnbondingHeight: big.NewInt(20), MinSelfDelegation: big.NewInt(7), Active: false, Commission: c, Description: d} return v diff --git a/test/chain/main.go b/test/chain/main.go index d9c0c25d2..209f38542 100644 --- a/test/chain/main.go +++ b/test/chain/main.go @@ -125,7 +125,7 @@ func fundFaucetContract(chain *core.BlockChain) { amount := 720000 tx, _ := types.SignTx(types.NewTransaction(nonce+uint64(4), StakingAddress, 0, big.NewInt(int64(amount)), params.TxGas, nil, nil), types.HomesteadSigner{}, FaucetPriKey) txs = append(txs, tx) - err := contractworker.CommitTransactions(txs, testUserAddress) + err := contractworker.CommitTransactions(txs, nil, testUserAddress) if err != nil { fmt.Println(err) } @@ -163,7 +163,7 @@ func callFaucetContractToFundAnAddress(chain *core.BlockChain) { callEnc = append(callEnc, paddedAddress...) callfaucettx, _ := types.SignTx(types.NewTransaction(nonce+uint64(5), faucetContractAddress, 0, big.NewInt(0), params.TxGasContractCreation*10, nil, callEnc), types.HomesteadSigner{}, FaucetPriKey) - err = contractworker.CommitTransactions(types.Transactions{callfaucettx}, testUserAddress) + err = contractworker.CommitTransactions(types.Transactions{callfaucettx}, nil, testUserAddress) if err != nil { fmt.Println(err) } @@ -241,7 +241,7 @@ func playStaking(chain *core.BlockChain) { tx, _ := types.SignTx(types.NewTransaction(0, stakeContractAddress, 0, big.NewInt(int64(stake)), params.TxGas*5, nil, callEncl), types.HomesteadSigner{}, allRandomUserKey[i]) stakingtxns = append(stakingtxns, tx) } - err = contractworker.CommitTransactions(stakingtxns, common.Address{}) + err = contractworker.CommitTransactions(stakingtxns, nil, common.Address{}) if err != nil { fmt.Println(err) @@ -299,7 +299,7 @@ func playWithdrawStaking(chain *core.BlockChain) { withdrawstakingtxns = append(withdrawstakingtxns, tx) } - err = contractworker.CommitTransactions(withdrawstakingtxns, common.Address{}) + err = contractworker.CommitTransactions(withdrawstakingtxns, nil, common.Address{}) if err != nil { fmt.Println("error:") fmt.Println(err)