parent
f91c013ed2
commit
d659a1b952
@ -1,9 +0,0 @@ |
|||||||
### Staking Service |
|
||||||
|
|
||||||
Staking service is used to send a stake to beacon chain to get a ticket to join Harmony blockchain. |
|
||||||
|
|
||||||
Staking service first gets beacon info which is a result of networkinfo service, then create a staking transaction and send it to beacon chain. As we are currently using ethereum account model, the staking transaction is an ethereum transaction. In ethereum, to create a transaction, a client needs to call to Ethereum network to get the current account nonce (required), suggested gas (optional), and chainID (optional). |
|
||||||
|
|
||||||
For development purpose, after getting beacon info, the staking service of the new node should send a RPC request to beacon chain to get the account nonce as the beacon chain already has a client service which serves that request. In the long term, we should switch to send a gossip request to beancon chain instead of a RPC call. |
|
||||||
|
|
||||||
**TODO**: Rework on this matter. |
|
@ -1,261 +0,0 @@ |
|||||||
package staking |
|
||||||
|
|
||||||
import ( |
|
||||||
"math/big" |
|
||||||
"strings" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/crypto" |
|
||||||
"github.com/ethereum/go-ethereum/rlp" |
|
||||||
"github.com/ethereum/go-ethereum/rpc" |
|
||||||
protobuf "github.com/golang/protobuf/proto" |
|
||||||
"github.com/harmony-one/bls/ffi/go/bls" |
|
||||||
"github.com/harmony-one/harmony/internal/params" |
|
||||||
|
|
||||||
"github.com/harmony-one/harmony/accounts" |
|
||||||
"github.com/harmony-one/harmony/accounts/abi" |
|
||||||
proto "github.com/harmony-one/harmony/api/client/service/proto" |
|
||||||
proto_common "github.com/harmony-one/harmony/api/proto" |
|
||||||
"github.com/harmony-one/harmony/api/proto/message" |
|
||||||
msg_pb "github.com/harmony-one/harmony/api/proto/message" |
|
||||||
"github.com/harmony-one/harmony/common/denominations" |
|
||||||
"github.com/harmony-one/harmony/contracts" |
|
||||||
"github.com/harmony-one/harmony/core" |
|
||||||
"github.com/harmony-one/harmony/core/types" |
|
||||||
common2 "github.com/harmony-one/harmony/internal/common" |
|
||||||
"github.com/harmony-one/harmony/internal/genesis" |
|
||||||
hmykey "github.com/harmony-one/harmony/internal/keystore" |
|
||||||
"github.com/harmony-one/harmony/internal/utils" |
|
||||||
"github.com/harmony-one/harmony/p2p" |
|
||||||
"github.com/harmony-one/harmony/p2p/host" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
// WaitTime is the delay time for resending staking transaction if the previous transaction did not get approved.
|
|
||||||
WaitTime = 5 * time.Second |
|
||||||
// StakingContractAddress is the staking deployed contract address
|
|
||||||
StakingContractAddress = "TODO(minhdoan): Create a PR to generate staking contract address" |
|
||||||
// StakingAmount is the amount of stake to put
|
|
||||||
StakingAmount = 10 |
|
||||||
) |
|
||||||
|
|
||||||
// State is the state of staking service.
|
|
||||||
type State byte |
|
||||||
|
|
||||||
// Service is the staking service.
|
|
||||||
// Service requires private key here which is not a right design.
|
|
||||||
// In stead in the right design, the end-user who runs mining needs to provide signed tx to this service.
|
|
||||||
type Service struct { |
|
||||||
host p2p.Host |
|
||||||
stopChan chan struct{} |
|
||||||
stoppedChan chan struct{} |
|
||||||
account accounts.Account |
|
||||||
blsPublicKey *bls.PublicKey |
|
||||||
stakingAmount int64 |
|
||||||
state State |
|
||||||
beaconChain *core.BlockChain |
|
||||||
messageChan chan *msg_pb.Message |
|
||||||
} |
|
||||||
|
|
||||||
// New returns staking service.
|
|
||||||
func New(host p2p.Host, account accounts.Account, beaconChain *core.BlockChain, blsPublicKey *bls.PublicKey) *Service { |
|
||||||
return &Service{ |
|
||||||
host: host, |
|
||||||
stopChan: make(chan struct{}), |
|
||||||
stoppedChan: make(chan struct{}), |
|
||||||
blsPublicKey: blsPublicKey, |
|
||||||
stakingAmount: StakingAmount, |
|
||||||
beaconChain: beaconChain, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// StartService starts staking service.
|
|
||||||
func (s *Service) StartService() { |
|
||||||
utils.Logger().Info().Msg("Start Staking Service") |
|
||||||
s.Run() |
|
||||||
} |
|
||||||
|
|
||||||
// Run runs staking.
|
|
||||||
func (s *Service) Run() { |
|
||||||
tick := time.NewTicker(WaitTime) |
|
||||||
go func() { |
|
||||||
defer close(s.stoppedChan) |
|
||||||
// Do service first time and after that doing it every 5 minutes.
|
|
||||||
// The reason we have to do it in every x minutes because of beacon chain syncing.
|
|
||||||
time.Sleep(WaitTime) |
|
||||||
s.DoService() |
|
||||||
for { |
|
||||||
select { |
|
||||||
case <-tick.C: |
|
||||||
if s.IsStaked() { |
|
||||||
return |
|
||||||
} |
|
||||||
//s.DoService()
|
|
||||||
return |
|
||||||
case <-s.stopChan: |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
}() |
|
||||||
} |
|
||||||
|
|
||||||
// IsStaked checks if the txn gets accepted and approved in the beacon chain.
|
|
||||||
func (s *Service) IsStaked() bool { |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
// DoService does staking.
|
|
||||||
func (s *Service) DoService() { |
|
||||||
utils.Logger().Info().Msg("Trying to send a staking transaction.") |
|
||||||
|
|
||||||
// TODO: no need to sync beacon chain to stake
|
|
||||||
//if s.beaconChain == nil {
|
|
||||||
// utils.Logger().Info().Msg("Can not send a staking transaction because of nil beacon chain.")
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
|
|
||||||
if msg := s.createStakingMessage(); msg == nil { |
|
||||||
utils.Logger().Error().Msg("Can not create staking transaction") |
|
||||||
} else if err := s.host.SendMessageToGroups([]p2p.GroupID{p2p.GroupIDBeacon}, host.ConstructP2pMessage(byte(17), msg)); err != nil { |
|
||||||
utils.Logger().Warn().Err(err).Msg("cannot send staking message") |
|
||||||
} else { |
|
||||||
utils.Logger().Info().Msg("Sent staking transaction to the network.") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (s *Service) getStakingInfo() *proto.StakingContractInfoResponse { |
|
||||||
address := s.account.Address |
|
||||||
state, err := s.beaconChain.State() |
|
||||||
if err != nil { |
|
||||||
utils.Logger().Error().Msg("error to get beacon chain state when getting staking info") |
|
||||||
return nil |
|
||||||
} |
|
||||||
balance := state.GetBalance(address) |
|
||||||
if balance == common.Big0 { |
|
||||||
utils.Logger().Error().Msg("account balance empty when getting staking info") |
|
||||||
return nil |
|
||||||
} |
|
||||||
nonce := state.GetNonce(address) |
|
||||||
if nonce == 0 { |
|
||||||
utils.Logger().Error().Msg("nonce zero when getting staking info") |
|
||||||
return nil |
|
||||||
} |
|
||||||
return &proto.StakingContractInfoResponse{ |
|
||||||
ContractAddress: StakingContractAddress, |
|
||||||
Balance: balance.Bytes(), |
|
||||||
Nonce: nonce, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (s *Service) getFakeStakingInfo() *proto.StakingContractInfoResponse { |
|
||||||
balance := big.NewInt(denominations.One) |
|
||||||
nonce := uint64(0) // TODO: make it a incrementing field
|
|
||||||
|
|
||||||
priKey := genesis.GenesisBeaconAccountPriKey |
|
||||||
contractAddress := crypto.PubkeyToAddress(priKey.PublicKey) |
|
||||||
|
|
||||||
stakingContractAddress := crypto.CreateAddress(contractAddress, uint64(nonce)) |
|
||||||
return &proto.StakingContractInfoResponse{ |
|
||||||
ContractAddress: common2.MustAddressToBech32(stakingContractAddress), |
|
||||||
Balance: balance.Bytes(), |
|
||||||
Nonce: nonce, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Constructs the staking message
|
|
||||||
func constructStakingMessage(ts types.Transactions) []byte { |
|
||||||
tsBytes, err := rlp.EncodeToBytes(ts) |
|
||||||
if err == nil { |
|
||||||
msg := &message.Message{ |
|
||||||
Type: message.MessageType_NEWNODE_BEACON_STAKING, |
|
||||||
Request: &message.Message_Staking{ |
|
||||||
Staking: &message.StakingRequest{ |
|
||||||
Transaction: tsBytes, |
|
||||||
NodeId: "", |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
if data, err := protobuf.Marshal(msg); err == nil { |
|
||||||
return data |
|
||||||
} |
|
||||||
} |
|
||||||
utils.Logger().Error().Err(err).Msg("Error when creating staking message") |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (s *Service) createRawStakingMessage() []byte { |
|
||||||
// TODO(minhdoan): Enable getStakingInfo back after testing.
|
|
||||||
stakingInfo := s.getFakeStakingInfo() |
|
||||||
toAddress := common2.ParseAddr(stakingInfo.ContractAddress) |
|
||||||
|
|
||||||
abi, err := abi.JSON(strings.NewReader(contracts.StakeLockContractABI)) |
|
||||||
if err != nil { |
|
||||||
utils.Logger().Error().Err(err).Msg("Failed to generate staking contract's ABI") |
|
||||||
} |
|
||||||
// TODO: the bls address should be signed by the bls private key
|
|
||||||
blsPubKeyBytes := s.blsPublicKey.Serialize() |
|
||||||
if len(blsPubKeyBytes) != 96 { |
|
||||||
utils.Logger().Error().Int("size", len(blsPubKeyBytes)).Msg("Wrong bls pubkey size") |
|
||||||
return []byte{} |
|
||||||
} |
|
||||||
blsPubKeyPart1 := [32]byte{} |
|
||||||
blsPubKeyPart2 := [32]byte{} |
|
||||||
blsPubKeyPart3 := [32]byte{} |
|
||||||
copy(blsPubKeyPart1[:], blsPubKeyBytes[:32]) |
|
||||||
copy(blsPubKeyPart2[:], blsPubKeyBytes[32:64]) |
|
||||||
copy(blsPubKeyPart3[:], blsPubKeyBytes[64:96]) |
|
||||||
bytesData, err := abi.Pack("lock", blsPubKeyPart1, blsPubKeyPart2, blsPubKeyPart3) |
|
||||||
|
|
||||||
if err != nil { |
|
||||||
utils.Logger().Error().Err(err).Msg("Failed to generate ABI function bytes data") |
|
||||||
} |
|
||||||
|
|
||||||
tx := types.NewTransaction( |
|
||||||
stakingInfo.Nonce, |
|
||||||
toAddress, |
|
||||||
0, |
|
||||||
big.NewInt(s.stakingAmount), |
|
||||||
params.TxGas*10, |
|
||||||
nil, |
|
||||||
bytesData, |
|
||||||
) |
|
||||||
|
|
||||||
// This is currently not called.
|
|
||||||
chainID := big.NewInt(1) // TODO: wire the correct chain ID after staking flow is revamped.
|
|
||||||
if signedTx, err := hmykey.SignTx(s.account, tx, chainID); err == nil { |
|
||||||
ts := types.Transactions{signedTx} |
|
||||||
return constructStakingMessage(ts) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (s *Service) createStakingMessage() []byte { |
|
||||||
if msg := s.createRawStakingMessage(); msg != nil { |
|
||||||
return proto_common.ConstructStakingMessage(msg) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// StopService stops staking service.
|
|
||||||
func (s *Service) StopService() { |
|
||||||
utils.Logger().Info().Msg("Stopping staking service.") |
|
||||||
s.stopChan <- struct{}{} |
|
||||||
<-s.stoppedChan |
|
||||||
utils.Logger().Info().Msg("Role conversion stopped.") |
|
||||||
} |
|
||||||
|
|
||||||
// NotifyService notify service
|
|
||||||
func (s *Service) NotifyService(params map[string]interface{}) { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// SetMessageChan sets up message channel to service.
|
|
||||||
func (s *Service) SetMessageChan(messageChan chan *msg_pb.Message) { |
|
||||||
s.messageChan = messageChan |
|
||||||
} |
|
||||||
|
|
||||||
// APIs for the services.
|
|
||||||
func (s *Service) APIs() []rpc.API { |
|
||||||
return nil |
|
||||||
} |
|
@ -1,91 +0,0 @@ |
|||||||
package node |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/hex" |
|
||||||
"math/big" |
|
||||||
|
|
||||||
"github.com/harmony-one/harmony/shard" |
|
||||||
|
|
||||||
"github.com/harmony-one/harmony/contracts/structs" |
|
||||||
|
|
||||||
"github.com/harmony-one/harmony/core" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
|
|
||||||
"github.com/harmony-one/harmony/internal/utils" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil" |
|
||||||
) |
|
||||||
|
|
||||||
//constants related to staking
|
|
||||||
//The first four bytes of the call data for a function call specifies the function to be called.
|
|
||||||
//It is the first (left, high-order in big-endian) four bytes of the Keccak-256 (SHA-3)
|
|
||||||
//Refer: https://solidity.readthedocs.io/en/develop/abi-spec.html
|
|
||||||
|
|
||||||
const ( |
|
||||||
funcSingatureBytes = 4 |
|
||||||
lockPeriodInEpochs = 3 // This should be in sync with contracts/StakeLockContract.sol
|
|
||||||
) |
|
||||||
|
|
||||||
// UpdateStakingList updates staking list from the given StakeInfo query result.
|
|
||||||
func (node *Node) UpdateStakingList(stakeInfoReturnValue *structs.StakeInfoReturnValue) { |
|
||||||
utils.Logger().Info().Interface("contractState", stakeInfoReturnValue).Msg("Updating staking list") |
|
||||||
if stakeInfoReturnValue == nil { |
|
||||||
return |
|
||||||
} |
|
||||||
node.CurrentStakes = make(map[common.Address]*structs.StakeInfo) |
|
||||||
for i, addr := range stakeInfoReturnValue.LockedAddresses { |
|
||||||
blockNum := stakeInfoReturnValue.BlockNums[i] |
|
||||||
lockPeriodCount := stakeInfoReturnValue.LockPeriodCounts[i] |
|
||||||
|
|
||||||
startEpoch := core.GetEpochFromBlockNumber(blockNum.Uint64()) |
|
||||||
curEpoch := core.GetEpochFromBlockNumber(node.Blockchain().CurrentBlock().NumberU64()) |
|
||||||
|
|
||||||
if startEpoch == curEpoch { |
|
||||||
continue // The token are counted into stakes at the beginning of next epoch.
|
|
||||||
} |
|
||||||
// True if the token is still staked within the locking period.
|
|
||||||
if curEpoch-startEpoch <= lockPeriodCount.Uint64()*lockPeriodInEpochs { |
|
||||||
blsPubKey := shard.BlsPublicKey{} |
|
||||||
copy(blsPubKey[:32], stakeInfoReturnValue.BlsPubicKeys1[i][:]) |
|
||||||
copy(blsPubKey[32:48], stakeInfoReturnValue.BlsPubicKeys2[i][:16]) |
|
||||||
node.CurrentStakes[addr] = &structs.StakeInfo{ |
|
||||||
Account: addr, |
|
||||||
BlsPublicKey: blsPubKey, |
|
||||||
BlockNum: blockNum, |
|
||||||
LockPeriodCount: lockPeriodCount, |
|
||||||
Amount: stakeInfoReturnValue.Amounts[i], |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (node *Node) printStakingList() { |
|
||||||
for addr, stakeInfo := range node.CurrentStakes { |
|
||||||
utils.Logger().Info(). |
|
||||||
Str("Address", addr.String()). |
|
||||||
Str("BlsPubKey", hex.EncodeToString(stakeInfo.BlsPublicKey[:])). |
|
||||||
Interface("BlockNum", stakeInfo.BlockNum). |
|
||||||
Interface("lockPeriodCount", stakeInfo.LockPeriodCount). |
|
||||||
Interface("amount", stakeInfo.Amount). |
|
||||||
Msg("") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//The first four bytes of the call data for a function call specifies the function to be called.
|
|
||||||
//It is the first (left, high-order in big-endian) four bytes of the Keccak-256 (SHA-3)
|
|
||||||
//Refer: https://solidity.readthedocs.io/en/develop/abi-spec.html
|
|
||||||
func decodeStakeCall(getData []byte) int64 { |
|
||||||
value := new(big.Int) |
|
||||||
value.SetBytes(getData[funcSingatureBytes:]) //Escape the method call.
|
|
||||||
return value.Int64() |
|
||||||
} |
|
||||||
|
|
||||||
//The first four bytes of the call data for a function call specifies the function to be called.
|
|
||||||
//It is the first (left, high-order in big-endian) four bytes of the Keccak-256 (SHA-3)
|
|
||||||
//Refer: https://solidity.readthedocs.io/en/develop/abi-spec.html
|
|
||||||
//gets the function signature from data.
|
|
||||||
func decodeFuncSign(data []byte) string { |
|
||||||
funcSign := hexutil.Encode(data[:funcSingatureBytes]) //The function signature is first 4 bytes of data in ethereum
|
|
||||||
return funcSign |
|
||||||
} |
|
@ -1,76 +0,0 @@ |
|||||||
package node |
|
||||||
|
|
||||||
import ( |
|
||||||
"math/big" |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/harmony-one/harmony/consensus" |
|
||||||
"github.com/harmony-one/harmony/contracts/structs" |
|
||||||
"github.com/harmony-one/harmony/crypto/bls" |
|
||||||
"github.com/harmony-one/harmony/internal/utils" |
|
||||||
"github.com/harmony-one/harmony/p2p" |
|
||||||
"github.com/harmony-one/harmony/p2p/p2pimpl" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
amount = big.NewInt(10) |
|
||||||
blockNum = big.NewInt(15000) |
|
||||||
lockPeriodCount = big.NewInt(1) |
|
||||||
testAddress = common.Address{123} |
|
||||||
testBlsPubKey1 = [32]byte{} |
|
||||||
testBlsPubKey2 = [32]byte{} |
|
||||||
testBlsPubKey3 = [32]byte{} |
|
||||||
) |
|
||||||
|
|
||||||
func TestUpdateStakingList(t *testing.T) { |
|
||||||
blsKey := bls.RandPrivateKey() |
|
||||||
pubKey := blsKey.GetPublicKey() |
|
||||||
leader := p2p.Peer{IP: "127.0.0.1", Port: "9882", ConsensusPubKey: pubKey} |
|
||||||
priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") |
|
||||||
host, err := p2pimpl.NewHost(&leader, priKey) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("newhost failure: %v", err) |
|
||||||
} |
|
||||||
consensus, err := consensus.New(host, 0, leader, blsKey) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("Cannot craeate consensus: %v", err) |
|
||||||
} |
|
||||||
node := New(host, consensus, testDBFactory, false) |
|
||||||
node.BlockPeriod = 8 * time.Second |
|
||||||
|
|
||||||
for i := 0; i < 1; i++ { |
|
||||||
selectedTxs, selectedStakingTxs := node.getTransactionsForNewBlock(common.Address{}) |
|
||||||
node.Worker.CommitTransactions(selectedTxs, selectedStakingTxs, common.Address{}) |
|
||||||
block, err := node.Worker.FinalizeNewBlock([]byte{}, []byte{}, 0, common.Address{}, nil, nil) |
|
||||||
|
|
||||||
// The block must first be finalized before being added to the blockchain.
|
|
||||||
if err != nil { |
|
||||||
t.Errorf("Error when finalizing block: %v", err) |
|
||||||
} |
|
||||||
block.Header() |
|
||||||
err = node.AddNewBlock(block) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("Error when adding new block: %v", err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
stakeInfo := &structs.StakeInfoReturnValue{ |
|
||||||
LockedAddresses: []common.Address{testAddress}, |
|
||||||
BlsPubicKeys1: [][32]byte{testBlsPubKey1}, |
|
||||||
BlsPubicKeys2: [][32]byte{testBlsPubKey2}, |
|
||||||
BlsPubicKeys3: [][32]byte{testBlsPubKey3}, |
|
||||||
BlockNums: []*big.Int{blockNum}, |
|
||||||
LockPeriodCounts: []*big.Int{lockPeriodCount}, |
|
||||||
Amounts: []*big.Int{amount}, |
|
||||||
} |
|
||||||
|
|
||||||
node.UpdateStakingList(stakeInfo) |
|
||||||
|
|
||||||
/* |
|
||||||
if node.CurrentStakes[testAddress].Amount.Cmp(amount) != 0 { |
|
||||||
t.Error("Stake Info is not updated correctly") |
|
||||||
} |
|
||||||
*/ |
|
||||||
} |
|
Loading…
Reference in new issue