diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/governance.go | 111 | ||||
-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 |
4 files changed, 230 insertions, 39 deletions
diff --git a/core/governance.go b/core/governance.go index db0e60b6c..68d5de821 100644 --- a/core/governance.go +++ b/core/governance.go @@ -1,16 +1,22 @@ package core import ( + "encoding/hex" "fmt" "math/big" "time" + coreCommon "github.com/dexon-foundation/dexon-consensus/common" dexCore "github.com/dexon-foundation/dexon-consensus/core" + coreCrypto "github.com/dexon-foundation/dexon-consensus/core/crypto" + coreEcdsa "github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa" coreTypes "github.com/dexon-foundation/dexon-consensus/core/types" dkgTypes "github.com/dexon-foundation/dexon-consensus/core/types/dkg" + "github.com/dexon-foundation/dexon/common" "github.com/dexon-foundation/dexon/core/state" "github.com/dexon-foundation/dexon/core/vm" + "github.com/dexon-foundation/dexon/crypto" "github.com/dexon-foundation/dexon/log" "github.com/dexon-foundation/dexon/rlp" ) @@ -41,11 +47,14 @@ func (g *governanceStateDB) StateAt(height uint64) (*state.StateDB, error) { } type Governance struct { - db GovernanceStateDB + db GovernanceStateDB + nodeSetCache *dexCore.NodeSetCache } func NewGovernance(db GovernanceStateDB) *Governance { - return &Governance{db: db} + g := &Governance{db: db} + g.nodeSetCache = dexCore.NewNodeSetCache(g) + return g } func (g *Governance) GetHeadState() *vm.GovernanceState { @@ -93,6 +102,43 @@ func (g *Governance) GetStateAtRound(round uint64) *vm.GovernanceState { return &vm.GovernanceState{StateDB: s} } +func (g *Governance) GetStateForDKGAtRound(round uint64) *vm.GovernanceState { + dkgRound := g.GetHeadState().DKGRound().Uint64() + if round > dkgRound { + return nil + } + if round == dkgRound { + return g.GetHeadState() + } + return g.GetStateAtRound(round) +} + +func (d *Governance) CRSRound() uint64 { + return d.GetHeadState().CRSRound().Uint64() +} + +// CRS returns the CRS for a given round. +func (d *Governance) CRS(round uint64) coreCommon.Hash { + if round <= dexCore.DKGDelayRound { + s := d.GetStateAtRound(0) + crs := s.CRS() + for i := uint64(0); i < round; i++ { + crs = crypto.Keccak256Hash(crs[:]) + } + return coreCommon.Hash(crs) + } + if round > d.CRSRound() { + return coreCommon.Hash{} + } + var s *vm.GovernanceState + if round == d.CRSRound() { + s = d.GetHeadState() + } else { + s = d.GetStateAtRound(round) + } + return coreCommon.Hash(s.CRS()) +} + func (g *Governance) Configuration(round uint64) *coreTypes.Config { configHelper := g.GetStateForConfigAtRound(round) c := configHelper.Configuration() @@ -110,15 +156,62 @@ func (g *Governance) GetRoundHeight(round uint64) uint64 { return g.GetHeadState().RoundHeight(big.NewInt(int64(round))).Uint64() } -func (g *Governance) GetStateForDKGAtRound(round uint64) *vm.GovernanceState { - dkgRound := g.GetHeadState().DKGRound().Uint64() - if round > dkgRound { - return nil +// NodeSet returns the current node set. +func (d *Governance) NodeSet(round uint64) []coreCrypto.PublicKey { + s := d.GetStateForConfigAtRound(round) + var pks []coreCrypto.PublicKey + + for _, n := range s.QualifiedNodes() { + pk, err := coreEcdsa.NewPublicKeyFromByteSlice(n.PublicKey) + if err != nil { + panic(err) + } + pks = append(pks, pk) } - if round == dkgRound { - return g.GetHeadState() + return pks +} + +func (d *Governance) NotarySet(round uint64) (map[string]struct{}, error) { + notarySet, err := d.nodeSetCache.GetNotarySet(round) + if err != nil { + return nil, err } - return g.GetStateAtRound(round) + + r := make(map[string]struct{}, len(notarySet)) + for id := range notarySet { + if key, exists := d.nodeSetCache.GetPublicKey(id); exists { + r[hex.EncodeToString(key.Bytes())] = struct{}{} + } + } + return r, nil +} + +func (d *Governance) NotarySetNodeKeyAddresses(round uint64) (map[common.Address]struct{}, error) { + notarySet, err := d.nodeSetCache.GetNotarySet(round) + if err != nil { + return nil, err + } + + r := make(map[common.Address]struct{}, len(notarySet)) + for id := range notarySet { + r[vm.IdToAddress(id)] = struct{}{} + } + return r, nil +} + +func (d *Governance) DKGSet(round uint64) (map[string]struct{}, error) { + dkgSet, err := d.nodeSetCache.GetDKGSet(round) + if err != nil { + return nil, err + } + + r := make(map[string]struct{}, len(dkgSet)) + for id := range dkgSet { + if key, exists := d.nodeSetCache.GetPublicKey(id); exists { + r[hex.EncodeToString(key.Bytes())] = struct{}{} + } + } + return r, nil } func (g *Governance) DKGComplaints(round uint64) []*dkgTypes.Complaint { 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) |