From 1b8bb239546f1703a6a0ada2211cc607b4c55b81 Mon Sep 17 00:00:00 2001
From: Wei-Ning Huang <w@dexon.org>
Date: Mon, 1 Apr 2019 19:42:50 +0800
Subject: core: vm: implement node public key replacement (#324)

---
 core/vm/oracle_contract_abi.go   | 31 ++++++++++++++++++++++++++++++
 core/vm/oracle_contracts.go      | 41 ++++++++++++++++++++++++++++++++++++++++
 core/vm/oracle_contracts_test.go | 31 +++++++++++++++++++++++++++++-
 3 files changed, 102 insertions(+), 1 deletion(-)

(limited to 'core')

diff --git a/core/vm/oracle_contract_abi.go b/core/vm/oracle_contract_abi.go
index 4572a74ae..799d0f28f 100644
--- a/core/vm/oracle_contract_abi.go
+++ b/core/vm/oracle_contract_abi.go
@@ -672,6 +672,23 @@ const GovernanceABIJSON = `
     "name": "NodeOwnershipTransfered",
     "type": "event"
   },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "NodeAddress",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "name": "PublicKey",
+        "type": "bytes"
+      }
+    ],
+    "name": "NodePublicKeyReplaced",
+    "type": "event"
+  },
   {
     "anonymous": false,
     "inputs": [
@@ -907,6 +924,20 @@ const GovernanceABIJSON = `
     "stateMutability": "nonpayable",
     "type": "function"
   },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "NewPublicKey",
+        "type": "bytes"
+      }
+    ],
+    "name": "replaceNodePublicKey",
+    "outputs": [],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
   {
     "constant": true,
     "inputs": [],
diff --git a/core/vm/oracle_contracts.go b/core/vm/oracle_contracts.go
index 9e148e4e8..0431f3823 100644
--- a/core/vm/oracle_contracts.go
+++ b/core/vm/oracle_contracts.go
@@ -1090,6 +1090,15 @@ func (s *GovernanceState) emitNodeOwnershipTransfered(nodeAddr, newNodeAddr comm
 	})
 }
 
+// event NodePublicKeyReplaced(address indexed NodeAddress, bytes PublicKey);
+func (s *GovernanceState) emitNodePublicKeyReplaced(nodeAddr common.Address, pk []byte) {
+	s.StateDB.AddLog(&types.Log{
+		Address: GovernanceContractAddress,
+		Topics:  []common.Hash{GovernanceABI.Events["NodePublicKeyReplaced"].Id(), nodeAddr.Hash()},
+		Data:    pk,
+	})
+}
+
 // event Staked(address indexed NodeAddress, uint256 Amount);
 func (s *GovernanceState) emitStaked(nodeAddr common.Address, amount *big.Int) {
 	s.StateDB.AddLog(&types.Log{
@@ -2415,6 +2424,12 @@ func (g *GovernanceContract) Run(evm *EVM, input []byte, contract *Contract) (re
 			return nil, errExecutionReverted
 		}
 		return res, nil
+	case "replaceNodePublicKey":
+		var pk []byte
+		if err := method.Inputs.Unpack(&pk, arguments); err != nil {
+			return nil, errExecutionReverted
+		}
+		return g.replaceNodePublicKey(pk)
 	case "roundHeight":
 		round := new(big.Int)
 		if err := method.Inputs.Unpack(&round, arguments); err != nil {
@@ -2481,6 +2496,32 @@ func (g *GovernanceContract) transferNodeOwnership(newOwner common.Address) ([]b
 	return nil, nil
 }
 
+func (g *GovernanceContract) replaceNodePublicKey(newPublicKey []byte) ([]byte, error) {
+	caller := g.contract.Caller()
+
+	offset := g.state.NodesOffsetByAddress(caller)
+	if offset.Cmp(big.NewInt(0)) < 0 {
+		return nil, errExecutionReverted
+	}
+
+	node := g.state.Node(offset)
+
+	_, err := publicKeyToNodeKeyAddress(newPublicKey)
+	if err != nil {
+		return nil, errExecutionReverted
+	}
+
+	g.state.PutNodeOffsets(node, big.NewInt(0))
+
+	node.PublicKey = newPublicKey
+	g.state.PutNodeOffsets(node, offset)
+	g.state.UpdateNode(offset, node)
+
+	g.state.emitNodePublicKeyReplaced(caller, newPublicKey)
+
+	return nil, nil
+}
+
 func PackProposeCRS(round uint64, signedCRS []byte) ([]byte, error) {
 	method := GovernanceABI.Name2Method["proposeCRS"]
 	res, err := method.Inputs.Pack(big.NewInt(int64(round)), signedCRS)
diff --git a/core/vm/oracle_contracts_test.go b/core/vm/oracle_contracts_test.go
index af80a7132..ae7755a06 100644
--- a/core/vm/oracle_contracts_test.go
+++ b/core/vm/oracle_contracts_test.go
@@ -301,7 +301,7 @@ func (g *OracleContractsTestSuite) TestTransferNodeOwnership() {
 	g.Require().Equal(offset.Uint64(), g.s.NodesOffsetByAddress(newAddr).Uint64())
 	g.Require().Equal(offset.Uint64(), g.s.NodesOffsetByNodeKeyAddress(newNodeKeyAddr).Uint64())
 
-	// Call with owner.
+	// New node for duplication test.
 	privKey2, addr2 := newPrefundAccount(g.stateDB)
 	pk2 := crypto.FromECDSAPub(&privKey2.PublicKey)
 	input, err = GovernanceABI.ABI.Pack("register", pk2, "Test2", "test1@dexon.org", "Taipei", "https://dexon.org")
@@ -316,6 +316,35 @@ func (g *OracleContractsTestSuite) TestTransferNodeOwnership() {
 	g.Require().Error(err)
 }
 
+func (g *OracleContractsTestSuite) TestReplaceNodePublicKey() {
+	privKey, addr := newPrefundAccount(g.stateDB)
+	pk := crypto.FromECDSAPub(&privKey.PublicKey)
+
+	amount := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e6))
+	input, err := GovernanceABI.ABI.Pack("register", pk, "Test1", "test1@dexon.org", "Taipei", "https://dexon.org")
+	g.Require().NoError(err)
+	_, err = g.call(GovernanceContractAddress, addr, input, amount)
+	g.Require().NoError(err)
+
+	offset := g.s.NodesOffsetByAddress(addr)
+
+	privKey2, addr2 := newPrefundAccount(g.stateDB)
+	pk2 := crypto.FromECDSAPub(&privKey2.PublicKey)
+
+	input, err = GovernanceABI.ABI.Pack("replaceNodePublicKey", pk2)
+	g.Require().NoError(err)
+
+	// Call with non-owner.
+	_, noneOwner := newPrefundAccount(g.stateDB)
+	_, err = g.call(GovernanceContractAddress, noneOwner, input, big.NewInt(0))
+	g.Require().Error(err)
+
+	// Call with owner.
+	_, err = g.call(GovernanceContractAddress, addr, input, big.NewInt(0))
+	g.Require().NoError(err)
+	g.Require().Equal(offset.Uint64(), g.s.NodesOffsetByNodeKeyAddress(addr2).Uint64())
+}
+
 func (g *OracleContractsTestSuite) TestStakingMechanism() {
 	privKey, addr := newPrefundAccount(g.stateDB)
 	pk := crypto.FromECDSAPub(&privKey.PublicKey)
-- 
cgit v1.2.3