From 8f51d08f4181672c372ae5f1a1e59734c35c25c2 Mon Sep 17 00:00:00 2001 From: Rongjian Lan Date: Mon, 25 Feb 2019 13:52:18 -0800 Subject: [PATCH 1/3] Reorg staking related code into staking.go --- node/node.go | 64 -------------------------------------------- node/staking.go | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 64 deletions(-) create mode 100644 node/staking.go diff --git a/node/node.go b/node/node.go index 929b4afa5..4b3b587f1 100644 --- a/node/node.go +++ b/node/node.go @@ -5,13 +5,11 @@ import ( "crypto/ecdsa" "encoding/binary" "fmt" - "math/big" "os" "sync" "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" @@ -456,68 +454,6 @@ func (node *Node) RemovePeersHandler() { } } -//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] - toAddress := txn.To() - if toAddress != nil && *toAddress != node.StakingContractAddress { //Not a address aimed at the staking contract. - continue - } - currentSender, _ := types.Sender(signerType, txn) - _, isPresent := node.CurrentStakes[currentSender] - data := txn.Data() - switch funcSignature := decodeFuncSign(data); funcSignature { - case depositFuncSignature: //deposit, currently: 0xd0e30db0 - amount := txn.Value() - value := amount.Int64() - if isPresent { - //This means the node has increased its stake. - node.CurrentStakes[currentSender] += value - } else { - //This means its a new node that is staking the first time. - node.CurrentStakes[currentSender] = value - } - case withdrawFuncSignature: //withdaw, currently: 0x2e1a7d4d - value := decodeStakeCall(data) - if isPresent { - if node.CurrentStakes[currentSender] > value { - node.CurrentStakes[currentSender] -= value - } else if node.CurrentStakes[currentSender] == value { - delete(node.CurrentStakes, currentSender) - } else { - continue //Overdraft protection. - } - } else { - continue //no-op: a node that is not staked cannot withdraw stake. - } - default: - continue //no-op if its not deposit or withdaw - } - } - return nil -} - -//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 -} - func (node *Node) initNodeConfiguration() (service.NodeConfig, chan p2p.Peer) { chanPeer := make(chan p2p.Peer) diff --git a/node/staking.go b/node/staking.go new file mode 100644 index 000000000..3177e6185 --- /dev/null +++ b/node/staking.go @@ -0,0 +1,71 @@ +package node + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/harmony-one/harmony/core/types" +) + +// UpdateStakingList updates the stakes of every node. +// TODO: read directly from smart contract, or at least check the receipt also for incompleted transaction. +func (node *Node) UpdateStakingList(block *types.Block) error { + signerType := types.HomesteadSigner{} + txns := block.Transactions() + for i := range txns { + txn := txns[i] + toAddress := txn.To() + if toAddress != nil && *toAddress != node.StakingContractAddress { //Not a address aimed at the staking contract. + continue + } + currentSender, _ := types.Sender(signerType, txn) + _, isPresent := node.CurrentStakes[currentSender] + data := txn.Data() + switch funcSignature := decodeFuncSign(data); funcSignature { + case depositFuncSignature: //deposit, currently: 0xd0e30db0 + amount := txn.Value() + value := amount.Int64() + if isPresent { + //This means the node has increased its stake. + node.CurrentStakes[currentSender] += value + } else { + //This means its a new node that is staking the first time. + node.CurrentStakes[currentSender] = value + } + case withdrawFuncSignature: //withdaw, currently: 0x2e1a7d4d + value := decodeStakeCall(data) + if isPresent { + if node.CurrentStakes[currentSender] > value { + node.CurrentStakes[currentSender] -= value + } else if node.CurrentStakes[currentSender] == value { + delete(node.CurrentStakes, currentSender) + } else { + continue //Overdraft protection. + } + } else { + continue //no-op: a node that is not staked cannot withdraw stake. + } + default: + continue //no-op if its not deposit or withdaw + } + } + return nil +} + +//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 +} From 44129b5c8e008e8943489269a78fa61455c6f2ff Mon Sep 17 00:00:00 2001 From: Rongjian Lan Date: Mon, 25 Feb 2019 13:53:59 -0800 Subject: [PATCH 2/3] More constants to staking.go --- node/node.go | 11 ----------- node/staking.go | 11 +++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/node/node.go b/node/node.go index 4b3b587f1..2997ad04a 100644 --- a/node/node.go +++ b/node/node.go @@ -99,17 +99,6 @@ type syncConfig struct { client *downloader.Client } -//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 ( - depositFuncSignature = "0xd0e30db0" - withdrawFuncSignature = "0x2e1a7d4d" - funcSingatureBytes = 4 -) - // Node represents a protocol-participating node in the network type Node struct { Consensus *consensus.Consensus // Consensus object containing all Consensus related data (e.g. committee members, signatures, commits) diff --git a/node/staking.go b/node/staking.go index 3177e6185..e4f35727f 100644 --- a/node/staking.go +++ b/node/staking.go @@ -7,6 +7,17 @@ import ( "github.com/harmony-one/harmony/core/types" ) +//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 ( + depositFuncSignature = "0xd0e30db0" + withdrawFuncSignature = "0x2e1a7d4d" + funcSingatureBytes = 4 +) + // UpdateStakingList updates the stakes of every node. // TODO: read directly from smart contract, or at least check the receipt also for incompleted transaction. func (node *Node) UpdateStakingList(block *types.Block) error { From 156f2dc6ae05c901f1bf3bbe8a44e1c0406925a1 Mon Sep 17 00:00:00 2001 From: Rongjian Lan Date: Mon, 25 Feb 2019 14:03:04 -0800 Subject: [PATCH 3/3] Also move tests for staking_test.go --- node/node_test.go | 102 --------------------------------------- node/staking_test.go | 111 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 102 deletions(-) create mode 100644 node/staking_test.go diff --git a/node/node_test.go b/node/node_test.go index 1c8036f3c..a5811e9ee 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -2,7 +2,6 @@ package node import ( "fmt" - "math/big" "os" "testing" "time" @@ -10,17 +9,12 @@ import ( "github.com/harmony-one/bls/ffi/go/bls" "github.com/harmony-one/harmony/drand" - "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) { @@ -185,99 +179,3 @@ 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) - initialStake := int64(1010) - node.CurrentStakes[Address] = initialStake //initial stake - - withdrawFnSignature := []byte("withdraw(uint256)") - hash := sha3.NewLegacyKeccak256() - hash.Write(withdrawFnSignature) - methodID := hash.Sum(nil)[:4] - - amount := "10" - stakeToWithdraw := new(big.Int) - stakeToWithdraw.SetString(amount, 10) - paddedAmount := common.LeftPadBytes(stakeToWithdraw.Bytes(), 32) - - remainingStakeShouldBe := initialStake - stakeToWithdraw.Int64() - - 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) - currentStake, ok := node.CurrentStakes[Address] - if !ok { - t.Error("The correct address was not present") - } - if currentStake != remainingStakeShouldBe { - t.Error("The correct stake value was not subtracted") - } - -} diff --git a/node/staking_test.go b/node/staking_test.go new file mode 100644 index 000000000..30b5c2f9e --- /dev/null +++ b/node/staking_test.go @@ -0,0 +1,111 @@ +package node + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/harmony-one/harmony/consensus" + "github.com/harmony-one/harmony/core/types" + "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 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) + initialStake := int64(1010) + node.CurrentStakes[Address] = initialStake //initial stake + + withdrawFnSignature := []byte("withdraw(uint256)") + hash := sha3.NewLegacyKeccak256() + hash.Write(withdrawFnSignature) + methodID := hash.Sum(nil)[:4] + + amount := "10" + stakeToWithdraw := new(big.Int) + stakeToWithdraw.SetString(amount, 10) + paddedAmount := common.LeftPadBytes(stakeToWithdraw.Bytes(), 32) + + remainingStakeShouldBe := initialStake - stakeToWithdraw.Int64() + + 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) + currentStake, ok := node.CurrentStakes[Address] + if !ok { + t.Error("The correct address was not present") + } + if currentStake != remainingStakeShouldBe { + t.Error("The correct stake value was not subtracted") + } +}