aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJimmy Hu <jimmy.hu@dexon.org>2019-02-15 09:45:50 +0800
committerWei-Ning Huang <w@dexon.org>2019-04-09 21:32:57 +0800
commitd03cc83dbd327d984ade86b8d52542ef0d5c636d (patch)
tree3dde8e42b1ba92fa144ae275741d60ac45869d54
parentb37c4f950d1005901e874eabcebeb5baf7797cfd (diff)
downloaddexon-d03cc83dbd327d984ade86b8d52542ef0d5c636d.tar
dexon-d03cc83dbd327d984ade86b8d52542ef0d5c636d.tar.gz
dexon-d03cc83dbd327d984ade86b8d52542ef0d5c636d.tar.bz2
dexon-d03cc83dbd327d984ade86b8d52542ef0d5c636d.tar.lz
dexon-d03cc83dbd327d984ade86b8d52542ef0d5c636d.tar.xz
dexon-d03cc83dbd327d984ade86b8d52542ef0d5c636d.tar.zst
dexon-d03cc83dbd327d984ade86b8d52542ef0d5c636d.zip
vm: reset DKG (#190)
* vm: Update gov abi * vm: Add DKGResetCount to state helper * vm: add getter * vm: Add DKGReset event * vm: Add resetDKG method * vm: check resetDKG criteria * vm: Add new CRS * vm: add helper pop2DByteArray * vm: emit event * vm: Add CoreMock to GovernanceContract * vm: bug fix * add test for resetDKG * vm: Add test * fix test * Modify mock interface
-rw-r--r--core/vm/oracle.go6
-rw-r--r--core/vm/oracle_contract_abi.go50
-rw-r--r--core/vm/oracle_contracts.go342
-rw-r--r--core/vm/oracle_contracts_test.go234
4 files changed, 562 insertions, 70 deletions
diff --git a/core/vm/oracle.go b/core/vm/oracle.go
index 493736e8c..14ba9b2f5 100644
--- a/core/vm/oracle.go
+++ b/core/vm/oracle.go
@@ -42,8 +42,10 @@ type OracleContract interface {
// A map representing available system oracle contracts.
var OracleContracts = map[common.Address]OracleContract{
- GovernanceContractAddress: &GovernanceContract{},
- NodeInfoOracleAddress: &NodeInfoOracleContract{},
+ GovernanceContractAddress: &GovernanceContract{
+ coreDKGUtils: &defaultCoreDKGUtils{},
+ },
+ NodeInfoOracleAddress: &NodeInfoOracleContract{},
}
// Run oracle contract.
diff --git a/core/vm/oracle_contract_abi.go b/core/vm/oracle_contract_abi.go
index 0f771a1a9..5c6f2a711 100644
--- a/core/vm/oracle_contract_abi.go
+++ b/core/vm/oracle_contract_abi.go
@@ -98,6 +98,25 @@ const GovernanceABIJSON = `
},
{
"constant": true,
+ "inputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "DKGResetCount",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
@@ -806,6 +825,23 @@ const GovernanceABIJSON = `
"type": "event"
},
{
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "Round",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "name": "BlockHeight",
+ "type": "uint256"
+ }
+ ],
+ "name": "DKGReset",
+ "type": "event"
+ },
+ {
"constant": false,
"inputs": [
{
@@ -1120,6 +1156,20 @@ const GovernanceABIJSON = `
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "NewSignedCRS",
+ "type": "bytes"
+ }
+ ],
+ "name": "resetDKG",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
}
]
`
diff --git a/core/vm/oracle_contracts.go b/core/vm/oracle_contracts.go
index 8e276f97c..b1a56606b 100644
--- a/core/vm/oracle_contracts.go
+++ b/core/vm/oracle_contracts.go
@@ -85,6 +85,7 @@ const (
minBlockIntervalLoc
fineValuesLoc
finedRecordsLoc
+ dkgResetCountLoc
)
func publicKeyToNodeID(pkBytes []byte) (Bytes32, error) {
@@ -205,6 +206,29 @@ func (s *GovernanceStateHelper) writeBytes(loc *big.Int, data []byte) {
}
}
+func (s *GovernanceStateHelper) eraseBytes(loc *big.Int) {
+ // Length of the dynamic array (bytes).
+ rawLength := s.getStateBigInt(loc)
+ lengthByte := new(big.Int).Mod(rawLength, big.NewInt(256))
+
+ // Bytes length <= 31, lengthByte % 2 == 0
+ // return the high 31 bytes.
+ if new(big.Int).Mod(lengthByte, big.NewInt(2)).Cmp(big.NewInt(0)) == 0 {
+ s.setStateBigInt(loc, big.NewInt(0))
+ return
+ }
+
+ // Actual length = (rawLength - 1) / 2
+ length := new(big.Int).Div(new(big.Int).Sub(
+ rawLength, big.NewInt(1)), big.NewInt(2)).Uint64()
+
+ // Fill 0.
+ s.writeBytes(loc, make([]byte, length))
+
+ // Clear slot.
+ s.setStateBigInt(loc, big.NewInt(0))
+}
+
func (s *GovernanceStateHelper) read2DByteArray(pos, index *big.Int) [][]byte {
baseLoc := s.getSlotLoc(pos)
loc := new(big.Int).Add(baseLoc, index)
@@ -220,6 +244,7 @@ func (s *GovernanceStateHelper) read2DByteArray(pos, index *big.Int) [][]byte {
return data
}
+
func (s *GovernanceStateHelper) appendTo2DByteArray(pos, index *big.Int, data []byte) {
// Find the loc of the last element.
baseLoc := s.getSlotLoc(pos)
@@ -235,6 +260,20 @@ func (s *GovernanceStateHelper) appendTo2DByteArray(pos, index *big.Int, data []
s.writeBytes(elementLoc, data)
}
+func (s *GovernanceStateHelper) erase2DByteArray(pos, index *big.Int) {
+ baseLoc := s.getSlotLoc(pos)
+ loc := new(big.Int).Add(baseLoc, index)
+
+ arrayLength := s.getStateBigInt(loc)
+ dataLoc := s.getSlotLoc(loc)
+
+ for i := int64(0); i < int64(arrayLength.Uint64()); i++ {
+ elementLoc := new(big.Int).Add(dataLoc, big.NewInt(i))
+ s.eraseBytes(elementLoc)
+ }
+ s.setStateBigInt(loc, big.NewInt(0))
+}
+
// uint256[] public roundHeight;
func (s *GovernanceStateHelper) RoundHeight(round *big.Int) *big.Int {
baseLoc := s.getSlotLoc(big.NewInt(roundHeightLoc))
@@ -570,6 +609,16 @@ func (s *GovernanceStateHelper) PushCRS(crs common.Hash) {
s.setState(common.BigToHash(loc), crs)
}
+func (s *GovernanceStateHelper) PopCRS() {
+ // decrease length by 1.
+ length := s.getStateBigInt(big.NewInt(crsLoc))
+ s.setStateBigInt(big.NewInt(crsLoc), new(big.Int).Sub(length, big.NewInt(1)))
+
+ baseLoc := s.getSlotLoc(big.NewInt(crsLoc))
+ loc := new(big.Int).Add(baseLoc, length)
+
+ s.setState(common.BigToHash(loc), common.Hash{})
+}
func (s *GovernanceStateHelper) Round() *big.Int {
return new(big.Int).Sub(s.getStateBigInt(big.NewInt(crsLoc)), big.NewInt(1))
}
@@ -614,6 +663,9 @@ func (s *GovernanceStateHelper) GetDKGMasterPublicKeyByProposerID(
}
return nil, errors.New("not found")
}
+func (s *GovernanceStateHelper) ClearDKGMasterPublicKeys(round *big.Int) {
+ s.erase2DByteArray(big.NewInt(dkgMasterPublicKeysLoc), round)
+}
// bytes[][] public dkgComplaints;
func (s *GovernanceStateHelper) DKGComplaints(round *big.Int) [][]byte {
@@ -622,6 +674,9 @@ func (s *GovernanceStateHelper) DKGComplaints(round *big.Int) [][]byte {
func (s *GovernanceStateHelper) PushDKGComplaint(round *big.Int, complaint []byte) {
s.appendTo2DByteArray(big.NewInt(dkgComplaintsLoc), round, complaint)
}
+func (s *GovernanceStateHelper) ClearDKGComplaints(round *big.Int) {
+ s.erase2DByteArray(big.NewInt(dkgComplaintsLoc), round)
+}
// mapping(address => bool)[] public dkgReady;
func (s *GovernanceStateHelper) DKGMPKReady(round *big.Int, addr common.Address) bool {
@@ -638,6 +693,16 @@ func (s *GovernanceStateHelper) PutDKGMPKReady(round *big.Int, addr common.Addre
}
s.setStateBigInt(mapLoc, res)
}
+func (s *GovernanceStateHelper) ClearDKGMPKReady(round *big.Int, dkgSet map[coreTypes.NodeID]struct{}) {
+ for id := range dkgSet {
+ offset := s.NodesOffsetByID(Bytes32(id.Hash))
+ if offset.Cmp(big.NewInt(0)) < 0 {
+ panic(errors.New("DKG node does not exist"))
+ }
+ node := s.Node(offset)
+ s.PutDKGMPKReady(round, node.Owner, false)
+ }
+}
// uint256[] public dkgReadysCount;
func (s *GovernanceStateHelper) DKGMPKReadysCount(round *big.Int) *big.Int {
@@ -649,6 +714,10 @@ func (s *GovernanceStateHelper) IncDKGMPKReadysCount(round *big.Int) {
count := s.getStateBigInt(loc)
s.setStateBigInt(loc, new(big.Int).Add(count, big.NewInt(1)))
}
+func (s *GovernanceStateHelper) ResetDKGMPKReadysCount(round *big.Int) {
+ loc := new(big.Int).Add(s.getSlotLoc(big.NewInt(dkgReadysCountLoc)), round)
+ s.setStateBigInt(loc, big.NewInt(0))
+}
// mapping(address => bool)[] public dkgFinalized;
func (s *GovernanceStateHelper) DKGFinalized(round *big.Int, addr common.Address) bool {
@@ -665,6 +734,16 @@ func (s *GovernanceStateHelper) PutDKGFinalized(round *big.Int, addr common.Addr
}
s.setStateBigInt(mapLoc, res)
}
+func (s *GovernanceStateHelper) ClearDKGFinalized(round *big.Int, dkgSet map[coreTypes.NodeID]struct{}) {
+ for id := range dkgSet {
+ offset := s.NodesOffsetByID(Bytes32(id.Hash))
+ if offset.Cmp(big.NewInt(0)) < 0 {
+ panic(errors.New("DKG node does not exist"))
+ }
+ node := s.Node(offset)
+ s.PutDKGFinalized(round, node.Owner, false)
+ }
+}
// uint256[] public dkgFinalizedsCount;
func (s *GovernanceStateHelper) DKGFinalizedsCount(round *big.Int) *big.Int {
@@ -676,6 +755,10 @@ func (s *GovernanceStateHelper) IncDKGFinalizedsCount(round *big.Int) {
count := s.getStateBigInt(loc)
s.setStateBigInt(loc, new(big.Int).Add(count, big.NewInt(1)))
}
+func (s *GovernanceStateHelper) ResetDKGFinalizedsCount(round *big.Int) {
+ loc := new(big.Int).Add(s.getSlotLoc(big.NewInt(dkgFinalizedsCountLoc)), round)
+ s.setStateBigInt(loc, big.NewInt(0))
+}
// address public owner;
func (s *GovernanceStateHelper) Owner() common.Address {
@@ -804,7 +887,7 @@ func (s *GovernanceStateHelper) SetFineValues(values []*big.Int) {
}
}
-// uint256[] public fineRdecords;
+// mapping(bytes32 => bool) public fineRdecords;
func (s *GovernanceStateHelper) FineRecords(recordHash Bytes32) bool {
loc := s.getMapLoc(big.NewInt(finedRecordsLoc), recordHash[:])
return s.getStateBigInt(loc).Cmp(big.NewInt(0)) > 0
@@ -818,6 +901,17 @@ func (s *GovernanceStateHelper) SetFineRecords(recordHash Bytes32, status bool)
s.setStateBigInt(loc, big.NewInt(value))
}
+// uint256[] public DKGResetCount;
+func (s *GovernanceStateHelper) DKGResetCount(round *big.Int) *big.Int {
+ arrayBaseLoc := s.getSlotLoc(big.NewInt(dkgResetCountLoc))
+ return s.getStateBigInt(new(big.Int).Add(arrayBaseLoc, round))
+}
+func (s *GovernanceStateHelper) IncDKGResetCount(round *big.Int) {
+ loc := new(big.Int).Add(s.getSlotLoc(big.NewInt(dkgResetCountLoc)), round)
+ count := s.getStateBigInt(loc)
+ s.setStateBigInt(loc, new(big.Int).Add(count, big.NewInt(1)))
+}
+
// Stake is a helper function for creating genesis state.
func (s *GovernanceStateHelper) Stake(
addr common.Address, publicKey []byte, staked *big.Int,
@@ -1053,11 +1147,74 @@ func (s *GovernanceStateHelper) emitFinePaid(nodeAddr common.Address, amount *bi
})
}
+// event DKGReset(uint256 indexed Round, uint256 BlockHeight);
+func (s *GovernanceStateHelper) emitDKGReset(round *big.Int, blockHeight *big.Int) {
+ s.StateDB.AddLog(&types.Log{
+ Address: GovernanceContractAddress,
+ Topics: []common.Hash{GovernanceABI.Events["DKGReset"].Id(), common.BigToHash(round)},
+ Data: common.BigToHash(blockHeight).Bytes(),
+ })
+}
+
+func getConfigState(evm *EVM, round *big.Int) (*GovernanceStateHelper, error) {
+ configRound := big.NewInt(0)
+ if round.Uint64() >= core.ConfigRoundShift {
+ configRound = new(big.Int).Sub(round, big.NewInt(int64(core.ConfigRoundShift-1)))
+ }
+
+ gs := &GovernanceStateHelper{evm.StateDB}
+ height := gs.RoundHeight(configRound).Uint64()
+ if round.Uint64() >= core.ConfigRoundShift {
+ if height == 0 {
+ return nil, errExecutionReverted
+ }
+ height--
+ }
+ statedb, err := evm.StateAtNumber(height)
+ return &GovernanceStateHelper{statedb}, err
+}
+
+type coreDKGUtils interface {
+ SetState(GovernanceStateHelper)
+ NewGroupPublicKey(*big.Int, int) (tsigVerifierIntf, error)
+}
+type tsigVerifierIntf interface {
+ VerifySignature(coreCommon.Hash, coreCrypto.Signature) bool
+}
+
// GovernanceContract represents the governance contract of DEXCON.
type GovernanceContract struct {
- evm *EVM
- state GovernanceStateHelper
- contract *Contract
+ evm *EVM
+ state GovernanceStateHelper
+ contract *Contract
+ coreDKGUtils coreDKGUtils
+}
+
+// defaultCoreDKGUtils implements coreDKGUtils.
+type defaultCoreDKGUtils struct {
+ state GovernanceStateHelper
+}
+
+func (c *defaultCoreDKGUtils) SetState(state GovernanceStateHelper) {
+ c.state = state
+}
+
+func (c *defaultCoreDKGUtils) NewGroupPublicKey(round *big.Int,
+ threshold int) (tsigVerifierIntf, error) {
+ // Prepare DKGMasterPublicKeys.
+ mpks := c.state.UniqueDKGMasterPublicKeys(round)
+
+ // Prepare DKGComplaints.
+ var complaints []*dkgTypes.Complaint
+ for _, comp := range c.state.DKGComplaints(round) {
+ x := new(dkgTypes.Complaint)
+ if err := rlp.DecodeBytes(comp, x); err != nil {
+ panic(err)
+ }
+ complaints = append(complaints, x)
+ }
+
+ return core.NewDKGGroupPublicKey(round.Uint64(), mpks, complaints, threshold)
}
func (g *GovernanceContract) Address() common.Address {
@@ -1085,21 +1242,21 @@ func (g *GovernanceContract) penalize() ([]byte, error) {
return nil, errExecutionReverted
}
-func (g *GovernanceContract) inDKGSet(round *big.Int, nodeID coreTypes.NodeID) bool {
- target := coreTypes.NewDKGSetTarget(coreCommon.Hash(g.state.CurrentCRS()))
- ns := coreTypes.NewNodeSet()
-
- configRound := big.NewInt(0) // If round < core.ConfigRoundShift, use 0.
- if round.Uint64() >= core.ConfigRoundShift {
- configRound = new(big.Int).Sub(round, big.NewInt(int64(core.ConfigRoundShift)))
+func (g *GovernanceContract) configDKGSetSize(round *big.Int) *big.Int {
+ s, err := getConfigState(g.evm, round)
+ if err != nil {
+ panic(err)
}
+ return s.DKGSetSize()
+}
+func (g *GovernanceContract) getDKGSet(round *big.Int) map[coreTypes.NodeID]struct{} {
+ target := coreTypes.NewDKGSetTarget(coreCommon.Hash(g.state.CRS(round)))
+ ns := coreTypes.NewNodeSet()
- statedb, err := g.evm.StateAtNumber(g.state.RoundHeight(configRound).Uint64())
+ state, err := getConfigState(g.evm, round)
if err != nil {
panic(err)
}
-
- state := GovernanceStateHelper{statedb}
for _, x := range state.QualifiedNodes() {
mpk, err := ecdsa.NewPublicKeyFromByteSlice(x.PublicKey)
if err != nil {
@@ -1107,8 +1264,11 @@ func (g *GovernanceContract) inDKGSet(round *big.Int, nodeID coreTypes.NodeID) b
}
ns.Add(coreTypes.NewNodeID(mpk))
}
+ return ns.GetSubSet(int(g.configDKGSetSize(round).Uint64()), target)
+}
- dkgSet := ns.GetSubSet(int(g.state.DKGSetSize().Uint64()), target)
+func (g *GovernanceContract) inDKGSet(round *big.Int, nodeID coreTypes.NodeID) bool {
+ dkgSet := g.getDKGSet(round)
_, ok := dkgSet[nodeID]
return ok
}
@@ -1557,23 +1717,10 @@ func (g *GovernanceContract) proposeCRS(nextRound *big.Int, signedCRS []byte) ([
prevCRS := g.state.CRS(round)
- // Prepare DKGMasterPublicKeys.
- dkgMasterPKs := g.state.UniqueDKGMasterPublicKeys(round)
-
- // Prepare DKGComplaints.
- var dkgComplaints []*dkgTypes.Complaint
- for _, comp := range g.state.DKGComplaints(round) {
- x := new(dkgTypes.Complaint)
- if err := rlp.DecodeBytes(comp, x); err != nil {
- panic(err)
- }
- dkgComplaints = append(dkgComplaints, x)
- }
-
threshold := int(g.state.DKGSetSize().Uint64()/3 + 1)
- dkgGPK, err := core.NewDKGGroupPublicKey(
- round.Uint64(), dkgMasterPKs, dkgComplaints, threshold)
+ dkgGPK, err := g.coreDKGUtils.NewGroupPublicKey(
+ round, threshold)
if err != nil {
return nil, errExecutionReverted
}
@@ -1684,6 +1831,101 @@ func (g *GovernanceContract) report(reportType *big.Int, arg1, arg2 []byte) ([]b
return nil, nil
}
+func (g *GovernanceContract) resetDKG(newSignedCRS []byte) ([]byte, error) {
+ // Check if current block over 80% of current round.
+ round := g.state.Round()
+ resetCount := g.state.DKGResetCount(round)
+ // Just restart DEXON if failed at round 0.
+ if round.Cmp(big.NewInt(0)) == 0 {
+ return nil, errExecutionReverted
+ }
+
+ // target = 80 + 100 * DKGResetCount
+ target := new(big.Int).Add(
+ big.NewInt(80),
+ new(big.Int).Mul(big.NewInt(100), resetCount))
+
+ // Round() is increased if CRS is signed. But we want to extend round r-1 now.
+ curRound := new(big.Int).Sub(round, big.NewInt(1))
+ roundHeight := g.state.RoundHeight(curRound)
+ gs, err := getConfigState(g.evm, curRound)
+ if err != nil {
+ return nil, err
+ }
+ config := gs.Configuration()
+
+ targetBlockNum := new(big.Int).SetUint64(config.RoundInterval / config.MinBlockInterval)
+ targetBlockNum.Mul(targetBlockNum, target)
+ targetBlockNum.Quo(targetBlockNum, big.NewInt(100))
+ targetBlockNum.Add(targetBlockNum, roundHeight)
+
+ blockHeight := g.evm.Context.BlockNumber
+ if blockHeight.Cmp(targetBlockNum) < 0 {
+ return nil, errExecutionReverted
+ }
+
+ // Check if next DKG did not success.
+ // Calculate 2f
+ threshold := new(big.Int).Mul(
+ big.NewInt(2),
+ new(big.Int).Div(g.state.DKGSetSize(), big.NewInt(3)))
+
+ // If 2f + 1 of DKG set is finalized, check if DKG succeeded.
+ if g.state.DKGFinalizedsCount(round).Cmp(threshold) > 0 {
+ _, err := g.coreDKGUtils.NewGroupPublicKey(
+ round, int(threshold.Int64()))
+ // DKG success.
+ if err == nil {
+ return nil, errExecutionReverted
+ }
+ switch err {
+ case core.ErrNotReachThreshold, core.ErrInvalidThreshold:
+ default:
+ return nil, errExecutionReverted
+ }
+ }
+ // Clear dkg states for next round.
+ dkgSet := g.getDKGSet(round)
+ g.state.ClearDKGMasterPublicKeys(round)
+ g.state.ClearDKGComplaints(round)
+ g.state.ClearDKGMPKReady(round, dkgSet)
+ g.state.ResetDKGMPKReadysCount(round)
+ g.state.ClearDKGFinalized(round, dkgSet)
+ g.state.ResetDKGFinalizedsCount(round)
+ // Update CRS.
+ prevCRS := g.state.CRS(curRound)
+ for i := uint64(0); i < resetCount.Uint64()+1; i++ {
+ prevCRS = crypto.Keccak256Hash(prevCRS[:])
+ }
+
+ dkgGPK, err := g.coreDKGUtils.NewGroupPublicKey(
+ curRound, int(config.DKGSetSize/3+1))
+ if err != nil {
+ return nil, errExecutionReverted
+ }
+ signature := coreCrypto.Signature{
+ Type: "bls",
+ Signature: newSignedCRS,
+ }
+ if !dkgGPK.VerifySignature(coreCommon.Hash(prevCRS), signature) {
+ return g.penalize()
+ }
+
+ // Save new CRS into state and increase round.
+ newCRS := crypto.Keccak256(newSignedCRS)
+ crs := common.BytesToHash(newCRS)
+
+ g.state.PopCRS()
+ g.state.PushCRS(crs)
+ g.state.emitCRSProposed(round, crs)
+
+ // Increase reset count.
+ g.state.IncDKGResetCount(round)
+
+ g.state.emitDKGReset(round, blockHeight)
+ return nil, nil
+}
+
// Run executes governance contract.
func (g *GovernanceContract) Run(evm *EVM, input []byte, contract *Contract) (ret []byte, err error) {
if len(input) < 4 {
@@ -1694,6 +1936,7 @@ func (g *GovernanceContract) Run(evm *EVM, input []byte, contract *Contract) (re
g.evm = evm
g.state = GovernanceStateHelper{evm.StateDB}
g.contract = contract
+ g.coreDKGUtils.SetState(g.state)
// Parse input.
method, exists := GovernanceABI.Sig2Method[string(input[:4])]
@@ -1788,6 +2031,14 @@ func (g *GovernanceContract) Run(evm *EVM, input []byte, contract *Contract) (re
return nil, errExecutionReverted
}
return g.report(args.Type, args.Arg1, args.Arg2)
+ case "resetDKG":
+ args := struct {
+ NewSignedCRS []byte
+ }{}
+ if err := method.Inputs.Unpack(&args, arguments); err != nil {
+ return nil, errExecutionReverted
+ }
+ return g.resetDKG(args.NewSignedCRS)
case "stake":
args := struct {
PublicKey []byte
@@ -2116,6 +2367,16 @@ func (g *GovernanceContract) Run(evm *EVM, input []byte, contract *Contract) (re
return nil, errExecutionReverted
}
return res, nil
+ case "DKGResetCount":
+ round := new(big.Int)
+ if err := method.Inputs.Unpack(&round, arguments); err != nil {
+ return nil, errExecutionReverted
+ }
+ res, err := method.Outputs.Pack(g.state.DKGResetCount(round))
+ if err != nil {
+ return nil, errExecutionReverted
+ }
+ return res, nil
}
return nil, errExecutionReverted
}
@@ -2258,21 +2519,6 @@ func (g *NodeInfoOracleContract) Run(evm *EVM, input []byte, contract *Contract)
arguments := input[4:]
- getConfigState := func(round *big.Int) (*GovernanceStateHelper, error) {
- configRound := big.NewInt(0)
- if round.Uint64() >= core.ConfigRoundShift {
- configRound = new(big.Int).Sub(round, big.NewInt(int64(core.ConfigRoundShift)))
- }
-
- gs := &GovernanceStateHelper{evm.StateDB}
- height := gs.RoundHeight(configRound).Uint64()
- if round.Uint64() > 0 && height == 0 {
- return nil, errExecutionReverted
- }
- statedb, err := evm.StateAtNumber(height)
- return &GovernanceStateHelper{statedb}, err
- }
-
// Dispatch method call.
switch method.Name {
case "delegators":
@@ -2281,7 +2527,7 @@ func (g *NodeInfoOracleContract) Run(evm *EVM, input []byte, contract *Contract)
if err := method.Inputs.Unpack(&args, arguments); err != nil {
return nil, errExecutionReverted
}
- state, err := getConfigState(round)
+ state, err := getConfigState(evm, round)
if err != nil {
return nil, err
}
@@ -2297,7 +2543,7 @@ func (g *NodeInfoOracleContract) Run(evm *EVM, input []byte, contract *Contract)
if err := method.Inputs.Unpack(&args, arguments); err != nil {
return nil, errExecutionReverted
}
- state, err := getConfigState(round)
+ state, err := getConfigState(evm, round)
if err != nil {
return nil, err
}
@@ -2312,7 +2558,7 @@ func (g *NodeInfoOracleContract) Run(evm *EVM, input []byte, contract *Contract)
if err := method.Inputs.Unpack(&args, arguments); err != nil {
return nil, errExecutionReverted
}
- state, err := getConfigState(round)
+ state, err := getConfigState(evm, round)
if err != nil {
return nil, err
}
diff --git a/core/vm/oracle_contracts_test.go b/core/vm/oracle_contracts_test.go
index ce2ba825b..654070c57 100644
--- a/core/vm/oracle_contracts_test.go
+++ b/core/vm/oracle_contracts_test.go
@@ -46,7 +46,10 @@ func init() {
}
func randomBytes(minLength, maxLength int32) []byte {
- length := rand.Int31()%(maxLength-minLength) + minLength
+ length := minLength
+ if maxLength != minLength {
+ length += rand.Int31() % (maxLength - minLength)
+ }
b := make([]byte, length)
for i := range b {
b[i] = byte(65 + rand.Int31()%60)
@@ -69,7 +72,7 @@ func (g *GovernanceStateHelperTestSuite) SetupTest() {
g.s = &GovernanceStateHelper{statedb}
}
-func (g *GovernanceStateHelperTestSuite) TestReadWriteBytes() {
+func (g *GovernanceStateHelperTestSuite) TestReadWriteEraseBytes() {
for i := 0; i < 100; i++ {
// Short bytes.
loc := big.NewInt(rand.Int63())
@@ -77,6 +80,9 @@ func (g *GovernanceStateHelperTestSuite) TestReadWriteBytes() {
g.s.writeBytes(loc, data)
read := g.s.readBytes(loc)
g.Require().Equal(0, bytes.Compare(data, read))
+ g.s.eraseBytes(loc)
+ read = g.s.readBytes(loc)
+ g.Require().Len(read, 0)
// long bytes.
loc = big.NewInt(rand.Int63())
@@ -84,6 +90,31 @@ func (g *GovernanceStateHelperTestSuite) TestReadWriteBytes() {
g.s.writeBytes(loc, data)
read = g.s.readBytes(loc)
g.Require().Equal(0, bytes.Compare(data, read))
+ g.s.eraseBytes(loc)
+ read = g.s.readBytes(loc)
+ g.Require().Len(read, 0)
+ }
+}
+
+func (g *GovernanceStateHelperTestSuite) TestReadWriteErase2DArray() {
+ for i := 0; i < 50; i++ {
+ loc := big.NewInt(rand.Int63())
+ for j := 0; j < 50; j++ {
+ idx := big.NewInt(int64(j))
+ data := make([][]byte, 30)
+ for key := range data {
+ data[key] = randomBytes(3, 32)
+ g.s.appendTo2DByteArray(loc, idx, data[key])
+ }
+ read := g.s.read2DByteArray(loc, idx)
+ g.Require().Len(read, len(data))
+ for key := range data {
+ g.Require().Equal(0, bytes.Compare(data[key], read[key]))
+ }
+ g.s.erase2DByteArray(loc, idx)
+ read = g.s.read2DByteArray(loc, idx)
+ g.Require().Len(read, 0)
+ }
}
}
@@ -94,6 +125,7 @@ func TestGovernanceStateHelper(t *testing.T) {
type OracleContractsTestSuite struct {
suite.Suite
+ context Context
config *params.DexconConfig
memDB *ethdb.MemDatabase
stateDB *state.StateDB
@@ -115,6 +147,7 @@ func (g *OracleContractsTestSuite) SetupTest() {
config.NextHalvingSupply = new(big.Int).Mul(big.NewInt(1e18), big.NewInt(2.5e9))
config.LastHalvedAmount = new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1.5e9))
config.MiningVelocity = 0.1875
+ config.DKGSetSize = 7
g.config = config
@@ -135,23 +168,8 @@ func (g *OracleContractsTestSuite) SetupTest() {
g.s.UpdateConfiguration(config)
g.stateDB.Commit(true)
-}
-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) {
-
- context := Context{
+ g.context = Context{
CanTransfer: func(db StateDB, addr common.Address, amount *big.Int) bool {
return db.GetBalance(addr).Cmp(amount) >= 0
},
@@ -173,11 +191,32 @@ func (g *OracleContractsTestSuite) call(
StateAtNumber: func(n uint64) (*state.StateDB, error) {
return g.stateDB, nil
},
- Time: big.NewInt(time.Now().UnixNano() / 1000000),
BlockNumber: big.NewInt(0),
}
- evm := NewEVM(context, g.stateDB, params.TestChainConfig, Config{IsBlockProposer: true})
+}
+
+func (g *OracleContractsTestSuite) TearDownTest() {
+ OracleContracts[GovernanceContractAddress].(*GovernanceContract).coreDKGUtils = &defaultCoreDKGUtils{}
+}
+
+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) {
+
+ g.context.Time = big.NewInt(time.Now().UnixNano() / 1000000)
+
+ evm := NewEVM(g.context, g.stateDB, params.TestChainConfig, Config{IsBlockProposer: true})
ret, _, err := evm.Call(AccountRef(caller), contractAddr, input, 10000000, value)
return ret, err
}
@@ -1058,6 +1097,161 @@ func (g *OracleContractsTestSuite) TestNodeInfoOracleContract() {
g.Require().Equal(0, int(value.Uint64()))
}
+type testCoreMock struct {
+ newDKGGPKError error
+ tsigReturn bool
+}
+
+func (m *testCoreMock) SetState(GovernanceStateHelper) {}
+
+func (m *testCoreMock) NewGroupPublicKey(*big.Int, int) (tsigVerifierIntf, error) {
+ if m.newDKGGPKError != nil {
+ return nil, m.newDKGGPKError
+ }
+ return &testTSigVerifierMock{m.tsigReturn}, nil
+}
+
+type testTSigVerifierMock struct {
+ ret bool
+}
+
+func (v *testTSigVerifierMock) VerifySignature(coreCommon.Hash, coreCrypto.Signature) bool {
+ return v.ret
+}
+
+func (g *OracleContractsTestSuite) TestResetDKG() {
+ for i := uint32(0); i < g.config.DKGSetSize; i++ {
+ privKey, addr := g.newPrefundAccount()
+ pk := crypto.FromECDSAPub(&privKey.PublicKey)
+
+ // Stake.
+ amount := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e6))
+ input, err := GovernanceABI.ABI.Pack("stake", pk, "Test1", "test1@dexon.org", "Taipei, Taiwan", "https://dexon.org")
+ g.Require().NoError(err)
+ _, err = g.call(GovernanceContractAddress, addr, input, amount)
+ g.Require().NoError(err)
+ }
+ g.Require().Len(g.s.QualifiedNodes(), int(g.config.DKGSetSize))
+
+ addrs := make(map[int][]common.Address)
+ addDKG := func(round int, final bool) {
+ addrs[round] = []common.Address{}
+ r := big.NewInt(int64(round))
+ target := coreTypes.NewDKGSetTarget(coreCommon.Hash(g.s.CRS(r)))
+ ns := coreTypes.NewNodeSet()
+
+ for _, x := range g.s.QualifiedNodes() {
+ mpk, err := coreEcdsa.NewPublicKeyFromByteSlice(x.PublicKey)
+ if err != nil {
+ panic(err)
+ }
+ ns.Add(coreTypes.NewNodeID(mpk))
+ }
+ dkgSet := ns.GetSubSet(int(g.s.DKGSetSize().Uint64()), target)
+ g.Require().Len(dkgSet, int(g.config.DKGSetSize))
+
+ for id := range dkgSet {
+ offset := g.s.NodesOffsetByID(Bytes32(id.Hash))
+ if offset.Cmp(big.NewInt(0)) < 0 {
+ panic("DKG node does not exist")
+ }
+ node := g.s.Node(offset)
+ // Prepare MPK.
+ g.s.PushDKGMasterPublicKey(r, randomBytes(32, 64))
+ // Prepare Complaint.
+ g.s.PushDKGComplaint(r, randomBytes(32, 64))
+ addr := node.Owner
+ addrs[round] = append(addrs[round], addr)
+ // Prepare MPK Ready.
+ g.s.PutDKGMPKReady(r, addr, true)
+ g.s.IncDKGMPKReadysCount(r)
+ if final {
+ // Prepare Finalized.
+ g.s.PutDKGFinalized(r, addr, true)
+ g.s.IncDKGFinalizedsCount(r)
+ }
+ }
+ dkgSetSize := len(dkgSet)
+ g.Require().Len(g.s.DKGMasterPublicKeys(r), dkgSetSize)
+ g.Require().Len(g.s.DKGComplaints(r), dkgSetSize)
+ g.Require().Equal(0, g.s.DKGMPKReadysCount(r).Cmp(big.NewInt(int64(dkgSetSize))))
+ for _, addr := range addrs[round] {
+ g.Require().True(g.s.DKGMPKReady(r, addr))
+ }
+ if final {
+ g.Require().Equal(0, g.s.DKGFinalizedsCount(r).Cmp(big.NewInt(int64(dkgSetSize))))
+ for _, addr := range addrs[round] {
+ g.Require().True(g.s.DKGFinalized(r, addr))
+ }
+ }
+ }
+
+ // Fill data for previous rounds.
+ roundHeight := int64(g.config.RoundInterval / g.config.MinBlockInterval)
+ round := 3
+ for i := 0; i <= round; i++ {
+ // Prepare CRS.
+ crs := common.BytesToHash(randomBytes(common.HashLength, common.HashLength))
+ g.s.PushCRS(crs)
+ // Prepare Round Height
+ if i != 0 {
+ g.s.PushRoundHeight(big.NewInt(int64(i) * roundHeight))
+ }
+ g.Require().Equal(0, g.s.LenCRS().Cmp(big.NewInt(int64(i+2))))
+ g.Require().Equal(crs, g.s.CurrentCRS())
+ }
+ for i := 0; i <= round; i++ {
+ addDKG(i, true)
+ }
+
+ mock := &testCoreMock{
+ tsigReturn: true,
+ }
+ OracleContracts[GovernanceContractAddress].(*GovernanceContract).coreDKGUtils = mock
+ repeat := 3
+ for r := 0; r < repeat; r++ {
+ addDKG(round+1, false)
+ // Add one finalized for test.
+ roundPlusOne := big.NewInt(int64(round + 1))
+ g.s.PutDKGFinalized(roundPlusOne, addrs[round+1][0], true)
+ g.s.IncDKGFinalizedsCount(roundPlusOne)
+
+ g.context.BlockNumber = big.NewInt(roundHeight*int64(round) + roundHeight*int64(r) + roundHeight*80/100)
+ _, addr := g.newPrefundAccount()
+ newCRS := randomBytes(common.HashLength, common.HashLength)
+ input, err := GovernanceABI.ABI.Pack("resetDKG", newCRS)
+ g.Require().NoError(err)
+ _, err = g.call(GovernanceContractAddress, addr, input, big.NewInt(0))
+ g.Require().NoError(err)
+
+ // Test if CRS is reset.
+ newCRSHash := crypto.Keccak256Hash(newCRS)
+ g.Require().Equal(0, g.s.LenCRS().Cmp(big.NewInt(int64(round+2))))
+ g.Require().Equal(newCRSHash, g.s.CurrentCRS())
+ g.Require().Equal(newCRSHash, g.s.CRS(big.NewInt(int64(round+1))))
+
+ // Test if MPK is purged.
+ g.Require().Len(g.s.DKGMasterPublicKeys(big.NewInt(int64(round+1))), 0)
+ // Test if MPKReady is purged.
+ g.Require().Equal(0,
+ g.s.DKGMPKReadysCount(big.NewInt(int64(round+1))).Cmp(big.NewInt(0)))
+ for _, addr := range addrs[round+1] {
+ g.Require().False(g.s.DKGMPKReady(big.NewInt(int64(round+1)), addr))
+ }
+ // Test if Complaint is purged.
+ g.Require().Len(g.s.DKGComplaints(big.NewInt(int64(round+1))), 0)
+ // Test if Finalized is purged.
+ g.Require().Equal(0,
+ g.s.DKGFinalizedsCount(big.NewInt(int64(round+1))).Cmp(big.NewInt(0)))
+ for _, addr := range addrs[round+1] {
+ g.Require().False(g.s.DKGFinalized(big.NewInt(int64(round+1)), addr))
+ }
+
+ g.Require().Equal(0,
+ g.s.DKGResetCount(roundPlusOne).Cmp(big.NewInt(int64(r+1))))
+ }
+}
+
func TestGovernanceContract(t *testing.T) {
suite.Run(t, new(OracleContractsTestSuite))
}