From 0e82ab2363f2f49884d29709268b1979fc24e919 Mon Sep 17 00:00:00 2001 From: Jimmy Hu Date: Fri, 15 Feb 2019 09:45:50 +0800 Subject: 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 --- core/vm/oracle.go | 6 +- core/vm/oracle_contract_abi.go | 50 ++++++ core/vm/oracle_contracts.go | 342 +++++++++++++++++++++++++++++++++------ core/vm/oracle_contracts_test.go | 234 ++++++++++++++++++++++++--- 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 @@ -96,6 +96,25 @@ const GovernanceABIJSON = ` "stateMutability": "view", "type": "function" }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "DKGResetCount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, { "constant": true, "inputs": [], @@ -805,6 +824,23 @@ const GovernanceABIJSON = ` "name": "FinePaid", "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)) } -- cgit v1.2.3