diff options
author | Wei-Ning Huang <w@dexon.org> | 2019-03-20 11:55:01 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@dexon.org> | 2019-04-09 21:32:58 +0800 |
commit | bbd2910687e9e5cd5c52e887f837f5c992ba2261 (patch) | |
tree | 5b76316f47ca4437fab3e0ae29dee4a112e2d805 /core/vm | |
parent | 614cca7f39b8d63a5da938e56509e6fa5fc467cf (diff) | |
download | dexon-bbd2910687e9e5cd5c52e887f837f5c992ba2261.tar dexon-bbd2910687e9e5cd5c52e887f837f5c992ba2261.tar.gz dexon-bbd2910687e9e5cd5c52e887f837f5c992ba2261.tar.bz2 dexon-bbd2910687e9e5cd5c52e887f837f5c992ba2261.tar.lz dexon-bbd2910687e9e5cd5c52e887f837f5c992ba2261.tar.xz dexon-bbd2910687e9e5cd5c52e887f837f5c992ba2261.tar.zst dexon-bbd2910687e9e5cd5c52e887f837f5c992ba2261.zip |
consensus: dexcon: disqualify dead node (#280)
Since a qualified node might fail stopped, we need to remove them from
qualified nodes to maintain network integrity. We do this by inspect the
previous round to see if there are dead nodes. A dead node is a notary
set node that does not propose any block in the previous round. We
disqualify them by fining them so their staked value is 1 wei below
minStake. This make them unqualified for being notary set in the follow
on rounds.
Diffstat (limited to 'core/vm')
-rw-r--r-- | core/vm/oracle_contract_abi.go | 21 | ||||
-rw-r--r-- | core/vm/oracle_contracts.go | 49 | ||||
-rw-r--r-- | core/vm/oracle_contracts_test.go | 88 |
3 files changed, 128 insertions, 30 deletions
diff --git a/core/vm/oracle_contract_abi.go b/core/vm/oracle_contract_abi.go index f0845eb7b..cd037b068 100644 --- a/core/vm/oracle_contract_abi.go +++ b/core/vm/oracle_contract_abi.go @@ -130,7 +130,7 @@ const GovernanceABIJSON = ` "type": "uint256" }, { - "name": "unstaked_at", + "name": "unstakedAt", "type": "uint256" } ], @@ -530,6 +530,25 @@ const GovernanceABIJSON = ` }, { "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "lastProposedHeight", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, "inputs": [], "name": "minGasPrice", "outputs": [ diff --git a/core/vm/oracle_contracts.go b/core/vm/oracle_contracts.go index f21700d86..cd11dc051 100644 --- a/core/vm/oracle_contracts.go +++ b/core/vm/oracle_contracts.go @@ -62,6 +62,7 @@ const ( nodesLoc nodesOffsetByAddressLoc nodesOffsetByNodeKeyAddressLoc + lastProposedHeightLoc crsRoundLoc crsLoc dkgRoundLoc @@ -332,7 +333,8 @@ func (s *GovernanceState) DecTotalStaked(amount *big.Int) { // string location; // string url; // uint256 unstaked; -// uint256 unstaked_at; +// uint256 unstakedAt; +// uint256 lastProposedHeight; // } // // Node[] nodes; @@ -543,6 +545,16 @@ func (s *GovernanceState) GetNodeByID(id coreTypes.NodeID) (*nodeInfo, error) { return node, nil } +// mapping(address => uint256) public lastProposedHeight; +func (s *GovernanceState) LastProposedHeight(addr common.Address) *big.Int { + loc := s.getMapLoc(big.NewInt(lastProposedHeightLoc), addr.Bytes()) + return s.getStateBigInt(loc) +} +func (s *GovernanceState) PutLastProposedHeight(addr common.Address, height *big.Int) { + loc := s.getMapLoc(big.NewInt(lastProposedHeightLoc), addr.Bytes()) + s.setStateBigInt(loc, height) +} + // uint256 public crsRound; func (s *GovernanceState) CRSRound() *big.Int { return s.getStateBigInt(big.NewInt(crsRoundLoc)) @@ -960,6 +972,31 @@ func (s *GovernanceState) Register( s.IncTotalStaked(staked) } +func (s *GovernanceState) Disqualify(n *nodeInfo) error { + nodeAddr, err := publicKeyToNodeKeyAddress(n.PublicKey) + if err != nil { + return err + } + + // Node might already been unstaked in the latest state. + offset := s.NodesOffsetByNodeKeyAddress(nodeAddr) + if offset.Cmp(big.NewInt(0)) < 0 { + return errors.New("node does not exist") + } + + // Fine the node so it's staked value is 1 wei under minStake. + node := s.Node(offset) + extra := new(big.Int).Sub(new(big.Int).Sub(node.Staked, node.Fined), s.MinStake()) + amount := new(big.Int).Add(extra, big.NewInt(1)) + + if amount.Cmp(big.NewInt(0)) > 0 { + node.Fined = new(big.Int).Add(node.Fined, amount) + s.UpdateNode(offset, node) + } + + return nil +} + const decimalMultiplier = 100000000.0 // Configuration returns the current configuration. @@ -2252,6 +2289,16 @@ func (g *GovernanceContract) Run(evm *EVM, input []byte, contract *Contract) (re return nil, errExecutionReverted } return res, nil + case "lastProposedHeight": + address := common.Address{} + if err := method.Inputs.Unpack(&address, arguments); err != nil { + return nil, errExecutionReverted + } + res, err := method.Outputs.Pack(g.state.LastProposedHeight(address)) + if err != nil { + return nil, errExecutionReverted + } + return res, nil case "lockupPeriod": res, err := method.Outputs.Pack(g.state.LockupPeriod()) if err != nil { diff --git a/core/vm/oracle_contracts_test.go b/core/vm/oracle_contracts_test.go index 4b25b39da..55876f9b2 100644 --- a/core/vm/oracle_contracts_test.go +++ b/core/vm/oracle_contracts_test.go @@ -59,10 +59,22 @@ func randomBytes(minLength, maxLength int32) []byte { return b } +func newPrefundAccount(s *state.StateDB) (*ecdsa.PrivateKey, common.Address) { + privKey, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + address := crypto.PubkeyToAddress(privKey.PublicKey) + + s.AddBalance(address, new(big.Int).Mul(big.NewInt(1e18), big.NewInt(2e6))) + return privKey, address +} + type GovernanceStateTestSuite struct { suite.Suite - s *GovernanceState + stateDB *state.StateDB + s *GovernanceState } func (g *GovernanceStateTestSuite) SetupTest() { @@ -71,7 +83,13 @@ func (g *GovernanceStateTestSuite) SetupTest() { if err != nil { panic(err) } + g.stateDB = statedb g.s = &GovernanceState{statedb} + + config := params.TestnetChainConfig.Dexcon + g.s.Initialize(config, new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e7))) + + statedb.Commit(true) } func (g *GovernanceStateTestSuite) TestReadWriteEraseBytes() { @@ -117,6 +135,31 @@ func (g *GovernanceStateTestSuite) TestReadWriteErase1DArray() { } } +func (g *GovernanceStateTestSuite) TestDisqualify() { + privKey, addr := newPrefundAccount(g.stateDB) + pk := crypto.FromECDSAPub(&privKey.PublicKey) + + g.s.Register(addr, pk, "Test", "test@dexon.org", "Taipei", "https://test.com", g.s.MinStake()) + + node := g.s.Node(big.NewInt(0)) + g.Require().Equal(uint64(0), node.Fined.Uint64()) + + // Disqualify + g.s.Disqualify(node) + node = g.s.Node(big.NewInt(0)) + g.Require().Equal(uint64(1), node.Fined.Uint64()) + + // Disqualify again should change nothing. + g.s.Disqualify(node) + node = g.s.Node(big.NewInt(0)) + g.Require().Equal(uint64(1), node.Fined.Uint64()) + + // Disqualify none exist node should return error. + privKey2, _ := newPrefundAccount(g.stateDB) + node.PublicKey = crypto.FromECDSAPub(&privKey2.PublicKey) + g.Require().Error(g.s.Disqualify(node)) +} + func TestGovernanceState(t *testing.T) { suite.Run(t, new(GovernanceStateTestSuite)) } @@ -203,17 +246,6 @@ func (g *OracleContractsTestSuite) TearDownTest() { } } -func (g *OracleContractsTestSuite) newPrefundAccount() (*ecdsa.PrivateKey, common.Address) { - privKey, err := crypto.GenerateKey() - if err != nil { - panic(err) - } - address := crypto.PubkeyToAddress(privKey.PublicKey) - - g.stateDB.AddBalance(address, new(big.Int).Mul(big.NewInt(1e18), big.NewInt(2e6))) - return privKey, address -} - func (g *OracleContractsTestSuite) call( contractAddr common.Address, caller common.Address, input []byte, value *big.Int) ([]byte, error) { @@ -225,7 +257,7 @@ func (g *OracleContractsTestSuite) call( } func (g *OracleContractsTestSuite) TestTransferOwnership() { - _, addr := g.newPrefundAccount() + _, addr := newPrefundAccount(g.stateDB) input, err := GovernanceABI.ABI.Pack("transferOwnership", addr) g.Require().NoError(err) @@ -241,7 +273,7 @@ func (g *OracleContractsTestSuite) TestTransferOwnership() { } func (g *OracleContractsTestSuite) TestTransferNodeOwnership() { - privKey, addr := g.newPrefundAccount() + privKey, addr := newPrefundAccount(g.stateDB) pk := crypto.FromECDSAPub(&privKey.PublicKey) nodeKeyAddr := crypto.PubkeyToAddress(privKey.PublicKey) @@ -253,14 +285,14 @@ func (g *OracleContractsTestSuite) TestTransferNodeOwnership() { offset := g.s.NodesOffsetByAddress(addr) - _, newAddr := g.newPrefundAccount() + _, newAddr := newPrefundAccount(g.stateDB) newNodeKeyAddr := crypto.PubkeyToAddress(privKey.PublicKey) input, err = GovernanceABI.ABI.Pack("transferNodeOwnership", newAddr) g.Require().NoError(err) // Call with non-owner. - _, noneOwner := g.newPrefundAccount() + _, noneOwner := newPrefundAccount(g.stateDB) _, err = g.call(GovernanceContractAddress, noneOwner, input, big.NewInt(0)) g.Require().Error(err) @@ -273,7 +305,7 @@ func (g *OracleContractsTestSuite) TestTransferNodeOwnership() { g.Require().Equal(offset.Uint64(), g.s.NodesOffsetByNodeKeyAddress(newNodeKeyAddr).Uint64()) // Call with owner. - privKey2, addr2 := g.newPrefundAccount() + 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") g.Require().NoError(err) @@ -288,7 +320,7 @@ func (g *OracleContractsTestSuite) TestTransferNodeOwnership() { } func (g *OracleContractsTestSuite) TestStakingMechanism() { - privKey, addr := g.newPrefundAccount() + privKey, addr := newPrefundAccount(g.stateDB) pk := crypto.FromECDSAPub(&privKey.PublicKey) // Register with some stake. @@ -374,7 +406,7 @@ func (g *OracleContractsTestSuite) TestStakingMechanism() { amount = new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e6)) // 2nd node Stake. - privKey2, addr2 := g.newPrefundAccount() + privKey2, addr2 := newPrefundAccount(g.stateDB) pk2 := crypto.FromECDSAPub(&privKey2.PublicKey) input, err = GovernanceABI.ABI.Pack("register", pk2, "Test2", "test2@dexon.org", "Taipei", "https://dexon.org") g.Require().NoError(err) @@ -440,7 +472,7 @@ func (g *OracleContractsTestSuite) TestStakingMechanism() { } func (g *OracleContractsTestSuite) TestFine() { - privKey, addr := g.newPrefundAccount() + privKey, addr := newPrefundAccount(g.stateDB) pk := crypto.FromECDSAPub(&privKey.PublicKey) // Stake. @@ -452,7 +484,7 @@ func (g *OracleContractsTestSuite) TestFine() { g.Require().Equal(1, len(g.s.QualifiedNodes())) g.Require().Equal(ownerStaked, g.s.Node(big.NewInt(0)).Staked) - _, finePayer := g.newPrefundAccount() + _, finePayer := newPrefundAccount(g.stateDB) amount := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(5e5)) // Paying to node without fine should fail. @@ -497,7 +529,7 @@ func (g *OracleContractsTestSuite) TestFine() { } func (g *OracleContractsTestSuite) TestUpdateConfiguration() { - _, addr := g.newPrefundAccount() + _, addr := newPrefundAccount(g.stateDB) input, err := GovernanceABI.ABI.Pack("updateConfiguration", new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e6)), @@ -523,7 +555,7 @@ func (g *OracleContractsTestSuite) TestUpdateConfiguration() { } func (g *OracleContractsTestSuite) TestConfigurationReading() { - _, addr := g.newPrefundAccount() + _, addr := newPrefundAccount(g.stateDB) // CRS. input, err := GovernanceABI.ABI.Pack("crs") @@ -657,7 +689,7 @@ func (g *OracleContractsTestSuite) TestConfigurationReading() { } func (g *OracleContractsTestSuite) TestReportForkVote() { - key, addr := g.newPrefundAccount() + key, addr := newPrefundAccount(g.stateDB) pkBytes := crypto.FromECDSAPub(&key.PublicKey) // Stake. @@ -723,7 +755,7 @@ func (g *OracleContractsTestSuite) TestReportForkVote() { } func (g *OracleContractsTestSuite) TestReportForkBlock() { - key, addr := g.newPrefundAccount() + key, addr := newPrefundAccount(g.stateDB) pkBytes := crypto.FromECDSAPub(&key.PublicKey) // Stake. @@ -800,7 +832,7 @@ func (g *OracleContractsTestSuite) TestReportForkBlock() { } func (g *OracleContractsTestSuite) TestMiscVariableReading() { - privKey, addr := g.newPrefundAccount() + privKey, addr := newPrefundAccount(g.stateDB) pk := crypto.FromECDSAPub(&privKey.PublicKey) input, err := GovernanceABI.ABI.Pack("totalSupply") @@ -898,7 +930,7 @@ func (v *testTSigVerifierMock) VerifySignature(coreCommon.Hash, coreCrypto.Signa func (g *OracleContractsTestSuite) TestResetDKG() { for i := uint32(0); i < g.config.DKGSetSize; i++ { - privKey, addr := g.newPrefundAccount() + privKey, addr := newPrefundAccount(g.stateDB) pk := crypto.FromECDSAPub(&privKey.PublicKey) // Stake. @@ -1033,7 +1065,7 @@ func (g *OracleContractsTestSuite) TestResetDKG() { g.context.BlockNumber = big.NewInt( roundHeight*int64(round) + roundHeight*int64(r) + roundHeight*80/100) - _, addr := g.newPrefundAccount() + _, addr := newPrefundAccount(g.stateDB) newCRS := randomBytes(common.HashLength, common.HashLength) input, err := GovernanceABI.ABI.Pack("resetDKG", newCRS) g.Require().NoError(err) |