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