aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorWei-Ning Huang <w@dexon.org>2019-01-06 23:21:54 +0800
committerWei-Ning Huang <w@byzantine-lab.io>2019-06-12 17:27:21 +0800
commitc6c8b6cfd083058a255d614db14df0e75fdd3482 (patch)
tree23fd25eec0a08923f2516fc688ad2487592ae928 /core
parent2b7671af8b76f64bcbe69d99509d454cb33be5ad (diff)
downloadgo-tangerine-c6c8b6cfd083058a255d614db14df0e75fdd3482.tar
go-tangerine-c6c8b6cfd083058a255d614db14df0e75fdd3482.tar.gz
go-tangerine-c6c8b6cfd083058a255d614db14df0e75fdd3482.tar.bz2
go-tangerine-c6c8b6cfd083058a255d614db14df0e75fdd3482.tar.lz
go-tangerine-c6c8b6cfd083058a255d614db14df0e75fdd3482.tar.xz
go-tangerine-c6c8b6cfd083058a255d614db14df0e75fdd3482.tar.zst
go-tangerine-c6c8b6cfd083058a255d614db14df0e75fdd3482.zip
core: vm: implement byzantine reporting mechanism (#128)
Diffstat (limited to 'core')
-rw-r--r--core/vm/governance.go431
-rw-r--r--core/vm/governance_test.go195
2 files changed, 578 insertions, 48 deletions
diff --git a/core/vm/governance.go b/core/vm/governance.go
index 6dccc8c7d..1df82f84c 100644
--- a/core/vm/governance.go
+++ b/core/vm/governance.go
@@ -18,7 +18,10 @@
package vm
import (
+ "bytes"
+ "errors"
"math/big"
+ "sort"
"strings"
"github.com/dexon-foundation/dexon/accounts/abi"
@@ -331,6 +334,25 @@ const GovernanceABIJSON = `
},
{
"constant": true,
+ "inputs": [
+ {
+ "name": "",
+ "type": "bytes32"
+ }
+ ],
+ "name": "nodesOffsetByID",
+ "outputs": [
+ {
+ "name": "",
+ "type": "int256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
"inputs": [],
"name": "roundInterval",
"outputs": [
@@ -345,6 +367,25 @@ const GovernanceABIJSON = `
},
{
"constant": true,
+ "inputs": [
+ {
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "nodesOffsetByAddress",
+ "outputs": [
+ {
+ "name": "",
+ "type": "int256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
"inputs": [],
"name": "owner",
"outputs": [
@@ -362,14 +403,14 @@ const GovernanceABIJSON = `
"inputs": [
{
"name": "",
- "type": "address"
+ "type": "bytes32"
}
],
- "name": "nodesOffset",
+ "name": "finedRecords",
"outputs": [
{
"name": "",
- "type": "int256"
+ "type": "bool"
}
],
"payable": false,
@@ -398,6 +439,25 @@ const GovernanceABIJSON = `
"type": "uint256"
}
],
+ "name": "fineValues",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
"name": "roundHeight",
"outputs": [
{
@@ -684,6 +744,10 @@ const GovernanceABIJSON = `
{
"name": "MinBlockInterval",
"type": "uint256"
+ },
+ {
+ "name": "FineValues",
+ "type": "uint256[]"
}
],
"name": "updateConfiguration",
@@ -927,6 +991,28 @@ const GovernanceABIJSON = `
"payable": true,
"stateMutability": "payable",
"type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "Type",
+ "type": "uint256"
+ },
+ {
+ "name": "Arg1",
+ "type": "bytes"
+ },
+ {
+ "name": "Arg2",
+ "type": "bytes"
+ }
+ ],
+ "name": "report",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
}
]
`
@@ -936,6 +1022,16 @@ var GovernanceContractName2Method map[string]abi.Method
var sig2Method map[string]abi.Method
var events map[string]abi.Event
+type Bytes32 [32]byte
+
+type ReportType uint64
+
+const (
+ ReportTypeInvalidDKG = iota
+ ReportTypeForkVote
+ ReportTypeForkBlock
+)
+
func init() {
var err error
@@ -1037,6 +1133,12 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) (ret []by
return nil, errExecutionReverted
}
return res, nil
+ case "payFine":
+ address := common.Address{}
+ if err := method.Inputs.Unpack(&address, arguments); err != nil {
+ return nil, errExecutionReverted
+ }
+ return g.payFine(address)
case "proposeCRS":
args := struct {
Round *big.Int
@@ -1046,6 +1148,16 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) (ret []by
return nil, errExecutionReverted
}
return g.proposeCRS(args.Round, args.SignedCRS)
+ case "report":
+ args := struct {
+ Type *big.Int
+ Arg1 []byte
+ Arg2 []byte
+ }{}
+ if err := method.Inputs.Unpack(&args, arguments); err != nil {
+ return nil, errExecutionReverted
+ }
+ return g.report(args.Type, args.Arg1, args.Arg2)
case "stake":
args := struct {
PublicKey []byte
@@ -1093,12 +1205,6 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) (ret []by
return nil, errExecutionReverted
}
return g.withdraw(address)
- case "payFine":
- address := common.Address{}
- if err := method.Inputs.Unpack(&address, arguments); err != nil {
- return nil, errExecutionReverted
- }
- return g.payFine(address)
// --------------------------------
// Solidity auto generated methods.
@@ -1234,6 +1340,28 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) (ret []by
return nil, errExecutionReverted
}
return res, nil
+ case "finedRecords":
+ record := Bytes32{}
+ if err := method.Inputs.Unpack(&record, arguments); err != nil {
+ return nil, errExecutionReverted
+ }
+ value := g.state.FineRecords(record)
+ res, err := method.Outputs.Pack(value)
+ if err != nil {
+ return nil, errExecutionReverted
+ }
+ return res, nil
+ case "fineValues":
+ index := new(big.Int)
+ if err := method.Inputs.Unpack(&index, arguments); err != nil {
+ return nil, errExecutionReverted
+ }
+ value := g.state.FineValue(index)
+ res, err := method.Outputs.Pack(value)
+ if err != nil {
+ return nil, errExecutionReverted
+ }
+ return res, nil
case "k":
res, err := method.Outputs.Pack(g.state.K())
if err != nil {
@@ -1289,12 +1417,22 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) (ret []by
return nil, errExecutionReverted
}
return res, nil
- case "nodesOffset":
+ case "nodesOffsetByAddress":
address := common.Address{}
if err := method.Inputs.Unpack(&address, arguments); err != nil {
return nil, errExecutionReverted
}
- res, err := method.Outputs.Pack(g.state.NodesOffset(address))
+ res, err := method.Outputs.Pack(g.state.NodesOffsetByAddress(address))
+ if err != nil {
+ return nil, errExecutionReverted
+ }
+ return res, nil
+ case "nodesOffsetByID":
+ var id Bytes32
+ if err := method.Inputs.Unpack(&id, arguments); err != nil {
+ return nil, errExecutionReverted
+ }
+ res, err := method.Outputs.Pack(g.state.NodesOffsetByID(id))
if err != nil {
return nil, errExecutionReverted
}
@@ -1341,7 +1479,8 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) (ret []by
const (
roundHeightLoc = iota
nodesLoc
- nodesOffsetLoc
+ nodesOffsetByAddressLoc
+ nodesOffsetByIDLoc
delegatorsLoc
delegatorsOffsetLoc
crsLoc
@@ -1365,8 +1504,19 @@ const (
dkgSetSizeLoc
roundIntervalLoc
minBlockIntervalLoc
+ fineValuesLoc
+ finedRecordsLoc
)
+func publicKeyToNodeID(pkBytes []byte) (Bytes32, error) {
+ pk, err := crypto.UnmarshalPubkey(pkBytes)
+ if err != nil {
+ return Bytes32{}, err
+ }
+ id := Bytes32(coreTypes.NewNodeID(ecdsa.NewPublicKeyFromECDSA(pk)).Hash)
+ return id, nil
+}
+
// State manipulation helper fro the governance contract.
type GovernanceStateHelper struct {
StateDB StateDB
@@ -1675,6 +1825,9 @@ func (s *GovernanceStateHelper) QualifiedNodes() []*nodeInfo {
var nodes []*nodeInfo
for i := int64(0); i < int64(s.LenNodes().Uint64()); i++ {
node := s.Node(big.NewInt(i))
+ if node.Unstaked {
+ continue
+ }
if new(big.Int).Sub(node.Staked, node.Fined).Cmp(s.MinStake()) >= 0 {
nodes = append(nodes, node)
}
@@ -1682,20 +1835,44 @@ func (s *GovernanceStateHelper) QualifiedNodes() []*nodeInfo {
return nodes
}
-// mapping(address => uint256) public nodeOffset;
-func (s *GovernanceStateHelper) NodesOffset(addr common.Address) *big.Int {
- loc := s.getMapLoc(big.NewInt(nodesOffsetLoc), addr.Bytes())
+// mapping(address => uint256) public nodeOffsetByAddress;
+func (s *GovernanceStateHelper) NodesOffsetByAddress(addr common.Address) *big.Int {
+ loc := s.getMapLoc(big.NewInt(nodesOffsetByAddressLoc), addr.Bytes())
return new(big.Int).Sub(s.getStateBigInt(loc), big.NewInt(1))
}
-func (s *GovernanceStateHelper) PutNodesOffset(addr common.Address, offset *big.Int) {
- loc := s.getMapLoc(big.NewInt(nodesOffsetLoc), addr.Bytes())
+func (s *GovernanceStateHelper) PutNodesOffsetByAddress(addr common.Address, offset *big.Int) {
+ loc := s.getMapLoc(big.NewInt(nodesOffsetByAddressLoc), addr.Bytes())
s.setStateBigInt(loc, new(big.Int).Add(offset, big.NewInt(1)))
}
-func (s *GovernanceStateHelper) DeleteNodesOffset(addr common.Address) {
- loc := s.getMapLoc(big.NewInt(nodesOffsetLoc), addr.Bytes())
+func (s *GovernanceStateHelper) DeleteNodesOffsetByAddress(addr common.Address) {
+ loc := s.getMapLoc(big.NewInt(nodesOffsetByAddressLoc), addr.Bytes())
s.setStateBigInt(loc, big.NewInt(0))
}
+// mapping(address => uint256) public nodeOffsetByID;
+func (s *GovernanceStateHelper) NodesOffsetByID(id Bytes32) *big.Int {
+ loc := s.getMapLoc(big.NewInt(nodesOffsetByIDLoc), id[:])
+ return new(big.Int).Sub(s.getStateBigInt(loc), big.NewInt(1))
+}
+func (s *GovernanceStateHelper) PutNodesOffsetByID(id Bytes32, offset *big.Int) {
+ loc := s.getMapLoc(big.NewInt(nodesOffsetByIDLoc), id[:])
+ s.setStateBigInt(loc, new(big.Int).Add(offset, big.NewInt(1)))
+}
+func (s *GovernanceStateHelper) DeleteNodesOffsetByID(id Bytes32) {
+ loc := s.getMapLoc(big.NewInt(nodesOffsetByIDLoc), id[:])
+ s.setStateBigInt(loc, big.NewInt(0))
+}
+
+func (s *GovernanceStateHelper) PutNodeOffsets(n *nodeInfo, offset *big.Int) error {
+ id, err := publicKeyToNodeID(n.PublicKey)
+ if err != nil {
+ return err
+ }
+ s.PutNodesOffsetByID(id, offset)
+ s.PutNodesOffsetByAddress(n.Owner, offset)
+ return nil
+}
+
// struct Delegator {
// address node;
// address owner;
@@ -1841,6 +2018,20 @@ func (s *GovernanceStateHelper) UniqueDKGMasterPublicKeys(round *big.Int) []*dkg
}
return dkgMasterPKs
}
+func (s *GovernanceStateHelper) GetDKGMasterPublicKeyByProposerID(
+ round *big.Int, proposerID coreTypes.NodeID) (*dkgTypes.MasterPublicKey, error) {
+
+ for _, mpk := range s.DKGMasterPublicKeys(round) {
+ x := new(dkgTypes.MasterPublicKey)
+ if err := rlp.DecodeBytes(mpk, x); err != nil {
+ panic(err)
+ }
+ if x.ProposerID.Equal(proposerID) {
+ return x, nil
+ }
+ }
+ return nil, errors.New("not found")
+}
// bytes[][] public dkgComplaints;
func (s *GovernanceStateHelper) DKGComplaints(round *big.Int) [][]byte {
@@ -1981,12 +2172,48 @@ func (s *GovernanceStateHelper) MinBlockInterval() *big.Int {
return s.getStateBigInt(big.NewInt(minBlockIntervalLoc))
}
+// uint256[] public fineValues;
+func (s *GovernanceStateHelper) FineValue(index *big.Int) *big.Int {
+ arrayBaseLoc := s.getSlotLoc(big.NewInt(fineValuesLoc))
+ return s.getStateBigInt(new(big.Int).Add(arrayBaseLoc, index))
+}
+func (s *GovernanceStateHelper) FineValues() []*big.Int {
+ len := s.getStateBigInt(big.NewInt(fineValuesLoc))
+ result := make([]*big.Int, len.Uint64())
+ for i := 0; i < int(len.Uint64()); i++ {
+ result[i] = s.FineValue(big.NewInt(int64(i)))
+ }
+ return result
+}
+func (s *GovernanceStateHelper) SetFineValues(values []*big.Int) {
+ s.setStateBigInt(big.NewInt(fineValuesLoc), big.NewInt(int64(len(values))))
+
+ arrayBaseLoc := s.getSlotLoc(big.NewInt(fineValuesLoc))
+ for i, v := range values {
+ s.setStateBigInt(new(big.Int).Add(arrayBaseLoc, big.NewInt(int64(i))), v)
+ }
+}
+
+// uint256[] 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
+}
+func (s *GovernanceStateHelper) SetFineRecords(recordHash Bytes32, status bool) {
+ loc := s.getMapLoc(big.NewInt(finedRecordsLoc), recordHash[:])
+ value := int64(0)
+ if status {
+ value = int64(1)
+ }
+ s.setStateBigInt(loc, big.NewInt(value))
+}
+
// Stake is a helper function for creating genesis state.
func (s *GovernanceStateHelper) Stake(
addr common.Address, publicKey []byte, staked *big.Int,
name, email, location, url string) {
offset := s.LenNodes()
- s.PushNode(&nodeInfo{
+ node := &nodeInfo{
Owner: addr,
PublicKey: publicKey,
Staked: staked,
@@ -1995,8 +2222,23 @@ func (s *GovernanceStateHelper) Stake(
Email: email,
Location: location,
Url: url,
+ }
+ s.PushNode(node)
+ if err := s.PutNodeOffsets(node, offset); err != nil {
+ panic(err)
+ }
+
+ if staked.Cmp(big.NewInt(0)) == 0 {
+ return
+ }
+
+ offset = s.LenDelegators(addr)
+ s.PushDelegator(addr, &delegatorInfo{
+ Owner: addr,
+ Value: staked,
+ UndelegatedAt: big.NewInt(0),
})
- s.PutNodesOffset(addr, offset)
+ s.PutDelegatorOffset(addr, addr, offset)
}
const phiRatioMultiplier = 1000000.0
@@ -2017,6 +2259,7 @@ func (s *GovernanceStateHelper) Configuration() *params.DexconConfig {
DKGSetSize: uint32(s.getStateBigInt(big.NewInt(dkgSetSizeLoc)).Uint64()),
RoundInterval: s.getStateBigInt(big.NewInt(roundIntervalLoc)).Uint64(),
MinBlockInterval: s.getStateBigInt(big.NewInt(minBlockIntervalLoc)).Uint64(),
+ FineValues: s.FineValues(),
}
}
@@ -2035,6 +2278,7 @@ func (s *GovernanceStateHelper) UpdateConfiguration(cfg *params.DexconConfig) {
s.setStateBigInt(big.NewInt(dkgSetSizeLoc), big.NewInt(int64(cfg.DKGSetSize)))
s.setStateBigInt(big.NewInt(roundIntervalLoc), big.NewInt(int64(cfg.RoundInterval)))
s.setStateBigInt(big.NewInt(minBlockIntervalLoc), big.NewInt(int64(cfg.MinBlockInterval)))
+ s.SetFineValues(cfg.FineValues)
}
type rawConfigStruct struct {
@@ -2051,6 +2295,7 @@ type rawConfigStruct struct {
DKGSetSize *big.Int
RoundInterval *big.Int
MinBlockInterval *big.Int
+ FineValues []*big.Int
}
// UpdateConfigurationRaw updates system configuration.
@@ -2068,6 +2313,7 @@ func (s *GovernanceStateHelper) UpdateConfigurationRaw(cfg *rawConfigStruct) {
s.setStateBigInt(big.NewInt(dkgSetSizeLoc), cfg.DKGSetSize)
s.setStateBigInt(big.NewInt(roundIntervalLoc), cfg.RoundInterval)
s.setStateBigInt(big.NewInt(minBlockIntervalLoc), cfg.MinBlockInterval)
+ s.SetFineValues(cfg.FineValues)
}
// event ConfigurationChanged();
@@ -2229,6 +2475,32 @@ func (g *GovernanceContract) addDKGComplaint(round *big.Int, comp []byte) ([]byt
return g.penalize()
}
+ mpk, err := g.state.GetDKGMasterPublicKeyByProposerID(
+ round, dkgComplaint.PrivateShare.ProposerID)
+ if err != nil {
+ return g.penalize()
+ }
+
+ // Verify DKG complaint is correct.
+ ok, err := coreUtils.VerifyDKGComplaint(&dkgComplaint, mpk)
+ if !ok || err != nil {
+ return g.penalize()
+ }
+
+ // Fine the attacker.
+ need, err := coreUtils.NeedPenaltyDKGPrivateShare(&dkgComplaint, mpk)
+ if err != nil {
+ return g.penalize()
+ }
+ if need {
+ fineValue := g.state.FineValue(big.NewInt(ReportTypeInvalidDKG))
+ offset := g.state.NodesOffsetByID(Bytes32(dkgComplaint.PrivateShare.ProposerID.Hash))
+ node := g.state.Node(offset)
+ if err := g.fine(node.Owner, fineValue, comp, nil); err != nil {
+ return g.penalize()
+ }
+ }
+
g.state.PushDKGComplaint(round, comp)
// Set this to relatively high to prevent spamming
@@ -2242,7 +2514,7 @@ func (g *GovernanceContract) addDKGMasterPublicKey(round *big.Int, mpk []byte) (
}
caller := g.contract.Caller()
- offset := g.state.NodesOffset(caller)
+ offset := g.state.NodesOffsetByAddress(caller)
// Can not add dkg mpk if not staked.
if offset.Cmp(big.NewInt(0)) < 0 {
@@ -2344,7 +2616,7 @@ func (g *GovernanceContract) addDKGFinalize(round *big.Int, finalize []byte) ([]
}
func (g *GovernanceContract) delegate(nodeAddr common.Address) ([]byte, error) {
- offset := g.state.NodesOffset(nodeAddr)
+ offset := g.state.NodesOffsetByAddress(nodeAddr)
if offset.Cmp(big.NewInt(0)) < 0 {
return nil, errExecutionReverted
}
@@ -2401,12 +2673,7 @@ func (g *GovernanceContract) stake(
}
caller := g.contract.Caller()
- offset := g.state.NodesOffset(caller)
-
- // Check if public key is valid.
- if _, err := crypto.UnmarshalPubkey(publicKey); err != nil {
- return g.penalize()
- }
+ offset := g.state.NodesOffsetByAddress(caller)
// Can not stake if already staked.
if offset.Cmp(big.NewInt(0)) >= 0 {
@@ -2414,7 +2681,7 @@ func (g *GovernanceContract) stake(
}
offset = g.state.LenNodes()
- g.state.PushNode(&nodeInfo{
+ node := &nodeInfo{
Owner: caller,
PublicKey: publicKey,
Staked: big.NewInt(0),
@@ -2423,8 +2690,11 @@ func (g *GovernanceContract) stake(
Email: email,
Location: location,
Url: url,
- })
- g.state.PutNodesOffset(caller, offset)
+ }
+ g.state.PushNode(node)
+ if err := g.state.PutNodeOffsets(node, offset); err != nil {
+ return g.penalize()
+ }
// Delegate fund to itself.
if g.contract.Value().Cmp(big.NewInt(0)) > 0 {
@@ -2438,7 +2708,7 @@ func (g *GovernanceContract) stake(
}
func (g *GovernanceContract) undelegateHelper(nodeAddr, caller common.Address) ([]byte, error) {
- nodeOffset := g.state.NodesOffset(nodeAddr)
+ nodeOffset := g.state.NodesOffsetByAddress(nodeAddr)
if nodeOffset.Cmp(big.NewInt(0)) < 0 {
return nil, errExecutionReverted
}
@@ -2475,7 +2745,7 @@ func (g *GovernanceContract) undelegate(nodeAddr common.Address) ([]byte, error)
func (g *GovernanceContract) withdraw(nodeAddr common.Address) ([]byte, error) {
caller := g.contract.Caller()
- nodeOffset := g.state.NodesOffset(nodeAddr)
+ nodeOffset := g.state.NodesOffsetByAddress(nodeAddr)
if nodeOffset.Cmp(big.NewInt(0)) < 0 {
return nil, errExecutionReverted
}
@@ -2523,9 +2793,11 @@ func (g *GovernanceContract) withdraw(nodeAddr common.Address) ([]byte, error) {
if offset.Cmp(lastIndex) != 0 {
lastNode := g.state.Node(lastIndex)
g.state.UpdateNode(offset, lastNode)
- g.state.PutNodesOffset(lastNode.Owner, offset)
+ if err := g.state.PutNodeOffsets(lastNode, offset); err != nil {
+ panic(err)
+ }
}
- g.state.DeleteNodesOffset(nodeAddr)
+ g.state.DeleteNodesOffsetByAddress(nodeAddr)
g.state.PopLastNode()
}
@@ -2534,7 +2806,7 @@ func (g *GovernanceContract) withdraw(nodeAddr common.Address) ([]byte, error) {
func (g *GovernanceContract) unstake() ([]byte, error) {
caller := g.contract.Caller()
- offset := g.state.NodesOffset(caller)
+ offset := g.state.NodesOffsetByAddress(caller)
if offset.Cmp(big.NewInt(0)) < 0 {
return nil, errExecutionReverted
}
@@ -2567,7 +2839,7 @@ func (g *GovernanceContract) unstake() ([]byte, error) {
func (g *GovernanceContract) payFine(nodeAddr common.Address) ([]byte, error) {
caller := g.contract.Caller()
- nodeOffset := g.state.NodesOffset(nodeAddr)
+ nodeOffset := g.state.NodesOffsetByAddress(nodeAddr)
if nodeOffset.Cmp(big.NewInt(0)) < 0 {
return nil, errExecutionReverted
}
@@ -2639,6 +2911,89 @@ func (g *GovernanceContract) proposeCRS(nextRound *big.Int, signedCRS []byte) ([
return g.useGas(0)
}
+type sortBytes [][]byte
+
+func (s sortBytes) Less(i, j int) bool {
+ return bytes.Compare(s[i], s[j]) < 0
+}
+
+func (s sortBytes) Swap(i, j int) {
+ s[i], s[j] = s[j], s[i]
+}
+
+func (s sortBytes) Len() int {
+ return len(s)
+}
+
+func (g *GovernanceContract) fine(nodeAddr common.Address, amount *big.Int, payloads ...[]byte) error {
+ sort.Sort(sortBytes(payloads))
+
+ hash := Bytes32(crypto.Keccak256Hash(payloads...))
+ if g.state.FineRecords(hash) {
+ return errors.New("already fined")
+ }
+ g.state.SetFineRecords(hash, true)
+
+ nodeOffset := g.state.NodesOffsetByAddress(nodeAddr)
+ if nodeOffset.Cmp(big.NewInt(0)) < 0 {
+ return errExecutionReverted
+ }
+
+ // Set fined value.
+ node := g.state.Node(nodeOffset)
+ node.Fined = new(big.Int).Add(node.Fined, amount)
+ g.state.UpdateNode(nodeOffset, node)
+
+ return nil
+}
+
+func (g *GovernanceContract) report(reportType *big.Int, arg1, arg2 []byte) ([]byte, error) {
+ typeEnum := ReportType(reportType.Uint64())
+ var reportedNodeID coreTypes.NodeID
+
+ switch typeEnum {
+ case ReportTypeForkVote:
+ vote1 := new(coreTypes.Vote)
+ if err := rlp.DecodeBytes(arg1, vote1); err != nil {
+ return g.penalize()
+ }
+ vote2 := new(coreTypes.Vote)
+ if err := rlp.DecodeBytes(arg2, vote2); err != nil {
+ return g.penalize()
+ }
+ need, err := coreUtils.NeedPenaltyForkVote(vote1, vote2)
+ if !need || err != nil {
+ return g.penalize()
+ }
+ reportedNodeID = vote1.ProposerID
+ case ReportTypeForkBlock:
+ block1 := new(coreTypes.Block)
+ if err := rlp.DecodeBytes(arg1, block1); err != nil {
+ return g.penalize()
+ }
+ block2 := new(coreTypes.Block)
+ if err := rlp.DecodeBytes(arg2, block2); err != nil {
+ return g.penalize()
+ }
+ need, err := coreUtils.NeedPenaltyForkBlock(block1, block2)
+ if !need || err != nil {
+ return g.penalize()
+ }
+ reportedNodeID = block1.ProposerID
+ default:
+ return g.penalize()
+ }
+
+ offset := g.state.NodesOffsetByID(Bytes32(reportedNodeID.Hash))
+ node := g.state.Node(offset)
+
+ fineValue := g.state.FineValue(reportType)
+ if err := g.fine(node.Owner, fineValue, arg1, arg2); err != nil {
+ return nil, errExecutionReverted
+ }
+ return nil, nil
+}
+
func (g *GovernanceContract) transferOwnership(newOwner common.Address) ([]byte, error) {
// Only owner can update configuration.
if g.contract.Caller() != g.state.Owner() {
diff --git a/core/vm/governance_test.go b/core/vm/governance_test.go
index a91aba7b6..4c5236565 100644
--- a/core/vm/governance_test.go
+++ b/core/vm/governance_test.go
@@ -22,14 +22,22 @@ import (
"crypto/ecdsa"
"math/big"
"math/rand"
+ "sort"
"testing"
"time"
+ coreCommon "github.com/dexon-foundation/dexon-consensus/common"
+ 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"
+ coreUtils "github.com/dexon-foundation/dexon-consensus/core/utils"
+
"github.com/dexon-foundation/dexon/common"
"github.com/dexon-foundation/dexon/core/state"
"github.com/dexon-foundation/dexon/crypto"
"github.com/dexon-foundation/dexon/ethdb"
"github.com/dexon-foundation/dexon/params"
+ "github.com/dexon-foundation/dexon/rlp"
"github.com/stretchr/testify/suite"
)
@@ -187,16 +195,15 @@ func (g *GovernanceContractTestSuite) TestStakeUnstakeWithoutExtraDelegators() {
pk := crypto.FromECDSAPub(&privKey.PublicKey)
// Stake.
- amount := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(5e4))
+ amount := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e5))
balanceBeforeStake := g.stateDB.GetBalance(addr)
input, err := abiObject.Pack("stake", pk, "Test1", "test1@dexon.org", "Taipei, Taiwan", "https://dexon.org")
g.Require().NoError(err)
_, err = g.call(addr, input, amount)
g.Require().NoError(err)
- // Node staked but staked fund < MinStake so is still unqualified.
g.Require().Equal(1, int(g.s.LenNodes().Uint64()))
- g.Require().Equal(0, len(g.s.QualifiedNodes()))
+ g.Require().Equal(1, len(g.s.QualifiedNodes()))
g.Require().Equal("Test1", g.s.Node(big.NewInt(0)).Name)
// Check balance.
@@ -235,7 +242,7 @@ func (g *GovernanceContractTestSuite) TestStakeUnstakeWithoutExtraDelegators() {
_, err = g.call(addr2, input, amount)
g.Require().NoError(err)
g.Require().Equal("Test2", g.s.Node(big.NewInt(0)).Name)
- g.Require().Equal(0, int(g.s.NodesOffset(addr2).Int64()))
+ g.Require().Equal(0, int(g.s.NodesOffsetByAddress(addr2).Int64()))
// 1st node Stake.
input, err = abiObject.Pack("stake", pk, "Test1", "test1@dexon.org", "Taipei, Taiwan", "https://dexon.org")
@@ -243,26 +250,35 @@ func (g *GovernanceContractTestSuite) TestStakeUnstakeWithoutExtraDelegators() {
_, err = g.call(addr, input, amount)
g.Require().NoError(err)
+ g.Require().Equal(2, len(g.s.QualifiedNodes()))
+
// 2nd node Unstake.
input, err = abiObject.Pack("unstake")
g.Require().NoError(err)
_, err = g.call(addr2, input, big.NewInt(0))
g.Require().NoError(err)
+ node := g.s.Node(big.NewInt(0))
+ g.Require().Equal("Test2", node.Name)
+ g.Require().Equal(true, node.Unstaked)
+ g.Require().Equal(1, len(g.s.QualifiedNodes()))
+
time.Sleep(time.Second * 2)
input, err = abiObject.Pack("withdraw", addr2)
g.Require().NoError(err)
_, err = g.call(addr2, input, big.NewInt(0))
g.Require().NoError(err)
+ g.Require().Equal(1, len(g.s.QualifiedNodes()))
g.Require().Equal(1, int(g.s.LenNodes().Uint64()))
g.Require().Equal("Test1", g.s.Node(big.NewInt(0)).Name)
- g.Require().Equal(-1, int(g.s.NodesOffset(addr2).Int64()))
+ g.Require().Equal(-1, int(g.s.NodesOffsetByAddress(addr2).Int64()))
// 1st node Unstake.
input, err = abiObject.Pack("unstake")
g.Require().NoError(err)
_, err = g.call(addr, input, big.NewInt(0))
g.Require().NoError(err)
+ g.Require().Equal(0, len(g.s.QualifiedNodes()))
time.Sleep(time.Second * 2)
input, err = abiObject.Pack("withdraw", addr)
g.Require().NoError(err)
@@ -270,7 +286,7 @@ func (g *GovernanceContractTestSuite) TestStakeUnstakeWithoutExtraDelegators() {
g.Require().NoError(err)
g.Require().Equal(0, int(g.s.LenNodes().Uint64()))
- g.Require().Equal(-1, int(g.s.NodesOffset(addr).Int64()))
+ g.Require().Equal(-1, int(g.s.NodesOffsetByAddress(addr).Int64()))
g.Require().Equal(-1, int(g.s.DelegatorsOffset(addr, addr).Int64()))
// Check balance.
@@ -424,7 +440,7 @@ func (g *GovernanceContractTestSuite) TestFine() {
g.Require().NotNil(err)
// Fined.
- offset := g.s.NodesOffset(addr)
+ offset := g.s.NodesOffsetByAddress(addr)
g.Require().True(offset.Cmp(big.NewInt(0)) >= 0)
node := g.s.Node(offset)
node.Fined = new(big.Int).Set(amount)
@@ -544,7 +560,8 @@ func (g *GovernanceContractTestSuite) TestUpdateConfiguration() {
input, err := abiObject.Pack("updateConfiguration",
new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e5)), big.NewInt(1000),
big.NewInt(1e18), big.NewInt(8000000), big.NewInt(6), big.NewInt(250), big.NewInt(2500),
- big.NewInt(0), big.NewInt(667000), big.NewInt(4), big.NewInt(4), big.NewInt(600000), big.NewInt(900))
+ big.NewInt(0), big.NewInt(667000), big.NewInt(4), big.NewInt(4), big.NewInt(600000), big.NewInt(900),
+ []*big.Int{big.NewInt(1), big.NewInt(1), big.NewInt(1)})
g.Require().NoError(err)
// Call with non-owner.
@@ -729,6 +746,149 @@ func (g *GovernanceContractTestSuite) TestConfigurationReading() {
g.Require().Equal(g.config.MinBlockInterval, value.Uint64())
}
+func (g *GovernanceContractTestSuite) TestReportForkVote() {
+ key, addr := g.newPrefundAccount()
+ pkBytes := crypto.FromECDSAPub(&key.PublicKey)
+
+ // Stake.
+ amount := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(5e4))
+ input, err := abiObject.Pack("stake", pkBytes, "Test1", "test1@dexon.org", "Taipei, Taiwan", "https://dexon.org")
+ g.Require().NoError(err)
+ _, err = g.call(addr, input, amount)
+ g.Require().NoError(err)
+
+ pubKey := coreEcdsa.NewPublicKeyFromECDSA(&key.PublicKey)
+ privKey := coreEcdsa.NewPrivateKeyFromECDSA(key)
+ vote1 := coreTypes.NewVote(coreTypes.VoteCom, coreCommon.NewRandomHash(), uint64(0))
+ vote1.ProposerID = coreTypes.NewNodeID(pubKey)
+
+ vote2 := vote1.Clone()
+ for vote2.BlockHash == vote1.BlockHash {
+ vote2.BlockHash = coreCommon.NewRandomHash()
+ }
+ vote1.Signature, err = privKey.Sign(coreUtils.HashVote(vote1))
+ g.Require().NoError(err)
+ vote2.Signature, err = privKey.Sign(coreUtils.HashVote(vote2))
+ g.Require().NoError(err)
+
+ vote1Bytes, err := rlp.EncodeToBytes(vote1)
+ g.Require().NoError(err)
+ vote2Bytes, err := rlp.EncodeToBytes(vote2)
+ g.Require().NoError(err)
+
+ // Report wrong type (fork block)
+ input, err = abiObject.Pack("report", big.NewInt(2), vote1Bytes, vote2Bytes)
+ g.Require().NoError(err)
+ _, err = g.call(addr, input, big.NewInt(0))
+ g.Require().Error(err)
+
+ input, err = abiObject.Pack("report", big.NewInt(1), vote1Bytes, vote2Bytes)
+ g.Require().NoError(err)
+ _, err = g.call(addr, input, big.NewInt(0))
+ g.Require().NoError(err)
+
+ node := g.s.Node(big.NewInt(0))
+ g.Require().Equal(node.Fined, g.s.FineValue(big.NewInt(1)))
+
+ // Duplicate report should fail.
+ input, err = abiObject.Pack("report", big.NewInt(1), vote1Bytes, vote2Bytes)
+ g.Require().NoError(err)
+ _, err = g.call(addr, input, big.NewInt(0))
+ g.Require().Error(err)
+
+ // Check if finedRecords is set.
+ payloads := [][]byte{vote1Bytes, vote2Bytes}
+ sort.Sort(sortBytes(payloads))
+
+ hash := Bytes32(crypto.Keccak256Hash(payloads...))
+ input, err = abiObject.Pack("finedRecords", hash)
+ g.Require().NoError(err)
+ res, err := g.call(addr, input, big.NewInt(0))
+ g.Require().NoError(err)
+
+ var value bool
+ err = abiObject.Unpack(&value, "finedRecords", res)
+ g.Require().NoError(err)
+ g.Require().True(value)
+}
+
+func (g *GovernanceContractTestSuite) TestReportForkBlock() {
+ key, addr := g.newPrefundAccount()
+ pkBytes := crypto.FromECDSAPub(&key.PublicKey)
+
+ // Stake.
+ amount := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(5e4))
+ input, err := abiObject.Pack("stake", pkBytes, "Test1", "test1@dexon.org", "Taipei, Taiwan", "https://dexon.org")
+ g.Require().NoError(err)
+ _, err = g.call(addr, input, amount)
+ g.Require().NoError(err)
+
+ privKey := coreEcdsa.NewPrivateKeyFromECDSA(key)
+ block1 := &coreTypes.Block{
+ ProposerID: coreTypes.NewNodeID(privKey.PublicKey()),
+ ParentHash: coreCommon.NewRandomHash(),
+ Timestamp: time.Now(),
+ }
+
+ block2 := block1.Clone()
+ for block2.ParentHash == block1.ParentHash {
+ block2.ParentHash = coreCommon.NewRandomHash()
+ }
+
+ hashBlock := func(block *coreTypes.Block) coreCommon.Hash {
+ block.PayloadHash = coreCrypto.Keccak256Hash(block.Payload)
+ var err error
+ block.Hash, err = coreUtils.HashBlock(block)
+ g.Require().NoError(err)
+ return block.Hash
+ }
+
+ block1.Signature, err = privKey.Sign(hashBlock(block1))
+ g.Require().NoError(err)
+ block2.Signature, err = privKey.Sign(hashBlock(block2))
+ g.Require().NoError(err)
+
+ block1Bytes, err := rlp.EncodeToBytes(block1)
+ g.Require().NoError(err)
+ block2Bytes, err := rlp.EncodeToBytes(block2)
+ g.Require().NoError(err)
+
+ // Report wrong type (fork vote)
+ input, err = abiObject.Pack("report", big.NewInt(1), block1Bytes, block2Bytes)
+ g.Require().NoError(err)
+ _, err = g.call(addr, input, big.NewInt(0))
+ g.Require().Error(err)
+
+ input, err = abiObject.Pack("report", big.NewInt(2), block1Bytes, block2Bytes)
+ g.Require().NoError(err)
+ _, err = g.call(addr, input, big.NewInt(0))
+ g.Require().NoError(err)
+
+ node := g.s.Node(big.NewInt(0))
+ g.Require().Equal(node.Fined, g.s.FineValue(big.NewInt(2)))
+
+ // Duplicate report should fail.
+ input, err = abiObject.Pack("report", big.NewInt(2), block1Bytes, block2Bytes)
+ g.Require().NoError(err)
+ _, err = g.call(addr, input, big.NewInt(0))
+ g.Require().Error(err)
+
+ // Check if finedRecords is set.
+ payloads := [][]byte{block1Bytes, block2Bytes}
+ sort.Sort(sortBytes(payloads))
+
+ hash := Bytes32(crypto.Keccak256Hash(payloads...))
+ input, err = abiObject.Pack("finedRecords", hash)
+ g.Require().NoError(err)
+ res, err := g.call(addr, input, big.NewInt(0))
+ g.Require().NoError(err)
+
+ var value bool
+ err = abiObject.Unpack(&value, "finedRecords", res)
+ g.Require().NoError(err)
+ g.Require().True(value)
+}
+
func (g *GovernanceContractTestSuite) TestMiscVariableReading() {
privKey, addr := g.newPrefundAccount()
pk := crypto.FromECDSAPub(&privKey.PublicKey)
@@ -769,11 +929,21 @@ func (g *GovernanceContractTestSuite) TestMiscVariableReading() {
g.Require().NoError(err)
g.Require().Equal(1, int(value.Uint64()))
- input, err = abiObject.Pack("nodesOffset", addr)
+ input, err = abiObject.Pack("nodesOffsetByAddress", addr)
g.Require().NoError(err)
res, err = g.call(addr, input, big.NewInt(0))
g.Require().NoError(err)
- err = abiObject.Unpack(&value, "nodesOffset", res)
+ err = abiObject.Unpack(&value, "nodesOffsetByAddress", res)
+ g.Require().NoError(err)
+ g.Require().Equal(0, int(value.Uint64()))
+
+ id, err := publicKeyToNodeID(pk)
+ g.Require().NoError(err)
+ input, err = abiObject.Pack("nodesOffsetByID", id)
+ g.Require().NoError(err)
+ res, err = g.call(addr, input, big.NewInt(0))
+ g.Require().NoError(err)
+ err = abiObject.Unpack(&value, "nodesOffsetByID", res)
g.Require().NoError(err)
g.Require().Equal(0, int(value.Uint64()))
@@ -797,6 +967,11 @@ func (g *GovernanceContractTestSuite) TestMiscVariableReading() {
err = abiObject.Unpack(&value, "delegatorsOffset", res)
g.Require().NoError(err)
g.Require().Equal(2, int(value.Uint64()))
+
+ input, err = abiObject.Pack("fineValues", big.NewInt(0))
+ g.Require().NoError(err)
+ res, err = g.call(addr, input, big.NewInt(0))
+ g.Require().NoError(err)
}
func TestGovernanceContract(t *testing.T) {