diff --git a/consensus/consensus.go b/consensus/consensus.go index dd96b8e53..60d067955 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -27,7 +27,6 @@ import ( "golang.org/x/crypto/sha3" proto_discovery "github.com/harmony-one/harmony/api/proto/discovery" - proto_node "github.com/harmony-one/harmony/api/proto/node" ) // Consensus is the main struct with all states and data related to consensus process. @@ -107,10 +106,6 @@ type Consensus struct { // List of offline Peers OfflinePeerList []p2p.Peer - - //List of nodes related to beaconchain funcs - WaitingNodes []proto_node.Info - ActiveNodes []proto_node.Info } // BFTBlockInfo send the latest block that was in BFT consensus process as well as its consensusID to state syncing diff --git a/node/contract.go b/node/contract.go index 03118bbf1..86647ee7a 100644 --- a/node/contract.go +++ b/node/contract.go @@ -4,7 +4,6 @@ import ( "crypto/ecdsa" "encoding/hex" "math/big" - "strconv" "strings" "github.com/ethereum/go-ethereum/common" @@ -13,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/internal/utils/contract" + "golang.org/x/crypto/sha3" ) // Constants related to smart contract. @@ -36,12 +36,13 @@ func (node *Node) AddStakingContractToPendingTransactions() { dataEnc := common.FromHex(StakingContractBinary) // Unsigned transaction to avoid the case of transaction address. mycontracttx, _ := types.SignTx(types.NewContractCreation(uint64(0), node.Consensus.ShardID, contractFunds, params.TxGasContractCreation*10, nil, dataEnc), types.HomesteadSigner{}, priKey) - node.ContractAddresses = append(node.ContractAddresses, crypto.CreateAddress(contractAddress, uint64(0))) + //node.StakingContractAddress = crypto.CreateAddress(contractAddress, uint64(0)) + node.StakingContractAddress = node.generateDeployedStakingContractAddress(mycontracttx, contractAddress) node.addPendingTransactions(types.Transactions{mycontracttx}) } //CreateStakingWithdrawTransaction creates a new withdraw stake transaction -func (node *Node) CreateStakingWithdrawTransaction(stake int) (*types.Transaction, error) { +func (node *Node) CreateStakingWithdrawTransaction(stake string) (*types.Transaction, error) { //These should be read from somewhere. DepositContractPriKey, _ := ecdsa.GenerateKey(crypto.S256(), strings.NewReader("Deposit Smart Contract Key")) //DepositContractPriKey is pk for contract DepositContractAddress := crypto.PubkeyToAddress(DepositContractPriKey.PublicKey) //DepositContractAddress is the address for the contract @@ -50,9 +51,19 @@ func (node *Node) CreateStakingWithdrawTransaction(stake int) (*types.Transactio log.Error("Failed to get chain state", "Error", err) } nonce := state.GetNonce(crypto.PubkeyToAddress(DepositContractPriKey.PublicKey)) - callingFunction := "0x2e1a7d4d" - contractData := callingFunction + hex.EncodeToString([]byte(strconv.Itoa(stake))) - dataEnc := common.FromHex(contractData) + //Following: https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/transfer_tokens.go + withdrawFnSignature := []byte("withdraw(uint)") + hash := sha3.NewLegacyKeccak256() + hash.Write(withdrawFnSignature) + methodID := hash.Sum(nil)[:4] + + amount := new(big.Int) + amount.SetString(stake, 10) + paddedAmount := common.LeftPadBytes(amount.Bytes(), 32) + + var dataEnc []byte + dataEnc = append(dataEnc, methodID...) + dataEnc = append(dataEnc, paddedAmount...) tx, err := types.SignTx(types.NewTransaction(nonce, DepositContractAddress, node.Consensus.ShardID, big.NewInt(0), params.TxGasContractCreation*10, nil, dataEnc), types.HomesteadSigner{}, node.AccountKey) return tx, err } diff --git a/node/node.go b/node/node.go index 418ab3bab..33d6a69e7 100644 --- a/node/node.go +++ b/node/node.go @@ -160,6 +160,11 @@ type Node struct { // Service manager. serviceManager *service_manager.Manager + //Staked Accounts and Contract + CurrentStakes map[common.Address]int64 //This will save the latest information about staked nodes. + StakingContractAddress common.Address + WithdrawStakeFunc []byte + //Node Account AccountKey *ecdsa.PrivateKey Address common.Address @@ -266,9 +271,12 @@ func New(host p2p.Host, consensus *bft.Consensus, db ethdb.Database) *Node { node.Worker = worker.New(params.TestChainConfig, chain, node.Consensus, pki.GetAddressFromPublicKey(node.SelfPeer.PubKey), node.Consensus.ShardID) node.AddFaucetContractToPendingTransactions() if node.Role == BeaconLeader { - node.AddStakingContractToPendingTransactions() + node.AddStakingContractToPendingTransactions() //This will save the latest information about staked nodes in current staked node.DepositToFakeAccounts() } + if node.Role == BeaconLeader || node.Role == BeaconValidator { + node.CurrentStakes = make(map[common.Address]int64) + } node.Consensus.ConsensusBlock = make(chan *bft.BFTBlockInfo) node.Consensus.VerifiedNewBlock = make(chan *types.Block) } @@ -292,6 +300,30 @@ func New(host p2p.Host, consensus *bft.Consensus, db ethdb.Database) *Node { return &node } +func (node *Node) getDeployedStakingContract() common.Address { + return node.StakingContractAddress +} + +//In order to get the deployed contract address of a contract, we need to find the nonce of the address that created it. +//(Refer: https://solidity.readthedocs.io/en/v0.5.3/introduction-to-smart-contracts.html#index-8) +// Then we can (re)create the deployed address. Trivially, this is 0 for us. +// The deployed contract address can also be obtained via the receipt of the contract creating transaction. +func (node *Node) generateDeployedStakingContractAddress(mycontracttx *types.Transaction, contractAddress common.Address) common.Address { + //Ideally we send the transaction to + + //Correct Way 1: + //node.SendTx(mycontracttx) + //receipts := node.worker.GetCurrentReceipts() + //deployedcontractaddress = recepits[len(receipts)-1].ContractAddress //get the address from the receipt + + //Correct Way 2: + //nonce := GetNonce(contractAddress) + //deployedAddress := crypto.CreateAddress(contractAddress, uint64(nonce)) + //deployedcontractaddress = recepits[len(receipts)-1].ContractAddress //get the address from the receipt + nonce := 0 + return crypto.CreateAddress(contractAddress, uint64(nonce)) +} + // IsOutOfSync checks whether the node is out of sync by comparing latest block with consensus block func (node *Node) IsOutOfSync(consensusBlockInfo *bft.BFTBlockInfo) bool { consensusBlock := consensusBlockInfo.Block @@ -585,11 +617,53 @@ func (node *Node) RemovePeersHandler() { } } -func (node *Node) getDeployedStakingContract() common.Address { - // TODO(ak): Please populate here - return common.Address{} +//UpdateStakingList updates the stakes of every node. +func (node *Node) UpdateStakingList(block *types.Block) error { + signerType := types.HomesteadSigner{} + txns := block.Transactions() + for i := range txns { + txn := txns[i] + value := txn.Value().Int64() + currentSender, _ := types.Sender(signerType, txn) + _, isPresent := node.CurrentStakes[currentSender] + toAddress := txn.To() + if *toAddress != node.StakingContractAddress { //Not a address aimed at the staking contract. + continue + } + //This should be based on a switch case on function signature. + //TODO (ak) https://github.com/harmony-one/harmony/issues/430 + if value > int64(0) { //If value >0 means its a staking deposit transaction + if isPresent { + //This means this node has increaserd its stake + node.CurrentStakes[currentSender] += value + } else { + node.CurrentStakes[currentSender] = value + } + } else { //This means node has withdrawn stake. + getData := txn.Data() + value := decodeStakeCall(getData) //Value being withdrawn + if isPresent { + //This means this node has increaserd its stake + if node.CurrentStakes[currentSender] > value { + node.CurrentStakes[currentSender] -= value + } else if node.CurrentStakes[currentSender] == value { + delete(node.CurrentStakes, currentSender) + } else { + continue //Overdraft protection. + } + } else { + node.CurrentStakes[currentSender] = value + } + } + } + return nil } +func decodeStakeCall(getData []byte) int64 { + value := new(big.Int) + value.SetBytes(getData[4:]) //Escape the method call. + return value.Int64() +} func (node *Node) setupForShardLeader() { // Register explorer service. node.serviceManager.RegisterService(service_manager.SupportExplorer, explorer.New(&node.SelfPeer)) diff --git a/node/node_handler.go b/node/node_handler.go index cfef4b76d..2cd45685f 100644 --- a/node/node_handler.go +++ b/node/node_handler.go @@ -241,10 +241,12 @@ func (node *Node) VerifyNewBlock(newBlock *types.Block) bool { // 1. add the new block to blockchain // 2. [leader] send new block to the client func (node *Node) PostConsensusProcessing(newBlock *types.Block) { + if node.Role == BeaconLeader || node.Role == BeaconValidator { + node.UpdateStakingList(newBlock) + } if node.Consensus.IsLeader { node.BroadcastNewBlock(newBlock) } - node.AddNewBlock(newBlock) } diff --git a/node/node_test.go b/node/node_test.go index 3e51c9040..d92484127 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -2,16 +2,22 @@ package node import ( "fmt" + "math/big" "os" "testing" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" proto_discovery "github.com/harmony-one/harmony/api/proto/discovery" "github.com/harmony-one/harmony/consensus" + "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/crypto/pki" "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/p2p" "github.com/harmony-one/harmony/p2p/p2pimpl" + "golang.org/x/crypto/sha3" ) func TestNewNode(t *testing.T) { @@ -171,3 +177,96 @@ func TestPingPongHandler(t *testing.T) { go exitServer() node.StartServer() } + +func TestUpdateStakingDeposit(t *testing.T) { + _, pubKey := utils.GenKey("1", "2") + leader := p2p.Peer{IP: "127.0.0.1", Port: "8882", PubKey: pubKey} + validator := p2p.Peer{IP: "127.0.0.1", Port: "8885"} + priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") + host, err := p2pimpl.NewHost(&leader, priKey) + if err != nil { + t.Fatalf("newhost failure: %v", err) + } + consensus := consensus.New(host, "0", []p2p.Peer{leader, validator}, leader) + + node := New(host, consensus, nil) + node.CurrentStakes = make(map[common.Address]int64) + + DepositContractPriKey, _ := crypto.GenerateKey() //DepositContractPriKey is pk for contract + DepositContractAddress := crypto.PubkeyToAddress(DepositContractPriKey.PublicKey) //DepositContractAddress is the address for the contract + node.StakingContractAddress = DepositContractAddress + node.AccountKey, _ = crypto.GenerateKey() + Address := crypto.PubkeyToAddress(node.AccountKey.PublicKey) + callingFunction := "0xd0e30db0" + amount := new(big.Int) + amount.SetString("10", 10) + dataEnc := common.FromHex(callingFunction) //Deposit Does not take a argument, stake is transferred via amount. + tx1, err := types.SignTx(types.NewTransaction(0, DepositContractAddress, node.Consensus.ShardID, amount, params.TxGasContractCreation*10, nil, dataEnc), types.HomesteadSigner{}, node.AccountKey) + + var txs []*types.Transaction + txs = append(txs, tx1) + header := &types.Header{Extra: []byte("hello")} + block := types.NewBlock(header, txs, nil) + node.UpdateStakingList(block) + if len(node.CurrentStakes) == 0 { + t.Error("New node's stake was not added") + } + value, ok := node.CurrentStakes[Address] + if !ok { + t.Error("The correct address was not added") + } + if value != 10 { + t.Error("The correct stake value was not added") + } +} + +func TestUpdateStakingWithdrawal(t *testing.T) { + _, pubKey := utils.GenKey("1", "2") + leader := p2p.Peer{IP: "127.0.0.1", Port: "8882", PubKey: pubKey} + validator := p2p.Peer{IP: "127.0.0.1", Port: "8885"} + priKey, _, _ := utils.GenKeyP2P("127.0.0.1", "9902") + host, err := p2pimpl.NewHost(&leader, priKey) + if err != nil { + t.Fatalf("newhost failure: %v", err) + } + consensus := consensus.New(host, "0", []p2p.Peer{leader, validator}, leader) + + node := New(host, consensus, nil) + node.CurrentStakes = make(map[common.Address]int64) + + DepositContractPriKey, _ := crypto.GenerateKey() //DepositContractPriKey is pk for contract + DepositContractAddress := crypto.PubkeyToAddress(DepositContractPriKey.PublicKey) //DepositContractAddress is the address for the contract + node.StakingContractAddress = DepositContractAddress + node.AccountKey, _ = crypto.GenerateKey() + Address := crypto.PubkeyToAddress(node.AccountKey.PublicKey) + node.CurrentStakes[Address] = int64(1010) + + withdrawFnSignature := []byte("withdraw(uint)") + hash := sha3.NewLegacyKeccak256() + hash.Write(withdrawFnSignature) + methodID := hash.Sum(nil)[:4] + + stake := "1000" + amount := new(big.Int) + amount.SetString(stake, 10) + paddedAmount := common.LeftPadBytes(amount.Bytes(), 32) + + var dataEnc []byte + dataEnc = append(dataEnc, methodID...) + dataEnc = append(dataEnc, paddedAmount...) + tx, err := types.SignTx(types.NewTransaction(0, DepositContractAddress, node.Consensus.ShardID, big.NewInt(0), params.TxGasContractCreation*10, nil, dataEnc), types.HomesteadSigner{}, node.AccountKey) + + var txs []*types.Transaction + txs = append(txs, tx) + header := &types.Header{Extra: []byte("hello")} + block := types.NewBlock(header, txs, nil) + node.UpdateStakingList(block) + value, ok := node.CurrentStakes[Address] + if !ok { + t.Error("The correct address was not present") + } + if value != 10 { + t.Error("The correct stake value was not subtracted") + } + +}