aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/governance.go111
-rw-r--r--core/vm/oracle_contract_abi.go21
-rw-r--r--core/vm/oracle_contracts.go49
-rw-r--r--core/vm/oracle_contracts_test.go88
4 files changed, 230 insertions, 39 deletions
diff --git a/core/governance.go b/core/governance.go
index da310a1cd..bb49efb53 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 c138a6b4a..ac6d7315c 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.
@@ -2248,6 +2285,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)