From 4beaa71af63e7d58aa0db3869b5d50a05e4b17d1 Mon Sep 17 00:00:00 2001 From: NathanBSC Date: Mon, 29 May 2023 09:44:03 +0800 Subject: [PATCH] feat: add a tool for submitting evidence of malicious voting --- cmd/maliciousvote-submit/README.md | 21 ++ .../json_encoding_helper.go | 72 ++++++ .../json_encoding_helper_test.go | 23 ++ cmd/maliciousvote-submit/main.go | 140 +++++++++++ cmd/maliciousvote-submit/slash_indicator.go | 217 ++++++++++++++++++ core/monitor/malicious_vote_monitor.go | 31 ++- core/types/vote.go | 40 ++++ 7 files changed, 534 insertions(+), 10 deletions(-) create mode 100644 cmd/maliciousvote-submit/README.md create mode 100644 cmd/maliciousvote-submit/json_encoding_helper.go create mode 100644 cmd/maliciousvote-submit/json_encoding_helper_test.go create mode 100644 cmd/maliciousvote-submit/main.go create mode 100644 cmd/maliciousvote-submit/slash_indicator.go diff --git a/cmd/maliciousvote-submit/README.md b/cmd/maliciousvote-submit/README.md new file mode 100644 index 0000000000..e773e592f0 --- /dev/null +++ b/cmd/maliciousvote-submit/README.md @@ -0,0 +1,21 @@ +## maliciousvote-submit +A tool for submitting the evidence of malicious voting + +### Options +``` +GLOBAL OPTIONS: + --sender value raw private key in hex format without 0x prefix; check permission on your own + --node value rpc endpoint, http,https,ws,wss,ipc are supported + --chainId value chainId, can get by eth_chainId (default: 0) + --evidence value params for submitFinalityViolationEvidence in json format; string + --help, -h show help + --version, -v print the version +``` +### Evidence +can be extracted from logs generated by MaliciousVoteMonitor + +### Example +``` +./build/bin/maliciousvote-summit --chainId 714 --sender 59ba8068eb256d520179e903f43dacf6d8d57d72bd306e1bd603fdb812345678 --node ws://localhost:8545 --evidence "{\"VoteA\":{\"SrcNum\":6948,\"SrcHash\":\"dc58ff5dca8deefb7b03904ef2837e5f8b0e84ec147f021d4ff08343635540d3\",\"TarNum\":6949,\"TarHash\":\"24726f05534dc55c36ecc364951025abada0defa6d1b53bcb6b637f583b59996\",\"Sig\":\"9379a0626f962b828ed21fb34a6b6de034a23651c2e0c12b907293cf8f21d4fdd559e6f9c7f450a4243d33ad7aa5783d0e51e70979631d82819c254dfb130dfe924f057f7e2b4e64195fc7562f1cb0c45486c9cc3e6cc5679b4c0b5744bf33b5\"},\"VoteB\":{\"SrcNum\":6947,\"SrcHash\":\"24726f05534dc55c36ecc364951025abada0defa6d1b53bcb6b637f583b59996\",\"TarNum\":6950,\"TarHash\":\"6257f70ea6439b84d910595064a6e44e55ba0f2abc0c887346c420a60a5ef119\",\"Sig\":\"af9b500877d64277e80eea7c42b8d6ae5744d715625344ef6ddc66fa4e1dcb3e94568c79e018239641b724bacaa93046052d13f87b655d58b7afecf4e31036d5eca911e8c7436deea68c1e64ef7ed527ed25416039e4e7352f9b089cfb86481f\"},\"VoteAddr\":\"98b94137e4e2d4e628dcbc4a05d554f44950a7498040d3276d49c265708229127cd20e48c773cdc7a898b3bb572a17bf\"}" +``` + diff --git a/cmd/maliciousvote-submit/json_encoding_helper.go b/cmd/maliciousvote-submit/json_encoding_helper.go new file mode 100644 index 0000000000..f37468c8bc --- /dev/null +++ b/cmd/maliciousvote-submit/json_encoding_helper.go @@ -0,0 +1,72 @@ +package main + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +func (s SlashIndicatorVoteData) ToWrapper() *types.SlashIndicatorVoteDataWrapper { + wrapper := &types.SlashIndicatorVoteDataWrapper{ + SrcNum: s.SrcNum, + TarNum: s.TarNum, + } + + if len(s.Sig) != types.BLSSignatureLength { + log.Crit("wrong length of sig", "wanted", types.BLSSignatureLength, "get", len(s.Sig)) + } + wrapper.SrcHash = common.Bytes2Hex(s.SrcHash[:]) + wrapper.TarHash = common.Bytes2Hex(s.TarHash[:]) + wrapper.Sig = common.Bytes2Hex(s.Sig) + return wrapper +} + +func (s *SlashIndicatorVoteData) FromWrapper(wrapper *types.SlashIndicatorVoteDataWrapper) { + if len(wrapper.SrcHash) != common.HashLength*2 { + log.Crit("wrong length of SrcHash", "wanted", common.HashLength*2, "get", len(wrapper.SrcHash)) + } + if len(wrapper.TarHash) != common.HashLength*2 { + log.Crit("wrong length of TarHash", "wanted", common.HashLength*2, "get", len(wrapper.TarHash)) + } + if len(wrapper.Sig) != types.BLSSignatureLength*2 { + log.Crit("wrong length of Sig", "wanted", types.BLSSignatureLength*2, "get", len(wrapper.Sig)) + } + + s.SrcNum = wrapper.SrcNum + s.TarNum = wrapper.TarNum + copy(s.SrcHash[:], common.Hex2Bytes(wrapper.SrcHash)) + copy(s.TarHash[:], common.Hex2Bytes(wrapper.TarHash)) + s.Sig = common.Hex2Bytes(wrapper.Sig) +} + +func (s SlashIndicatorFinalityEvidence) MarshalJSON() ([]byte, error) { + wrapper := &types.SlashIndicatorFinalityEvidenceWrapper{ + VoteA: *s.VoteA.ToWrapper(), + VoteB: *s.VoteB.ToWrapper(), + } + + if len(s.VoteAddr) != types.BLSPublicKeyLength { + log.Crit("wrong length of VoteAddr", "wanted", types.BLSPublicKeyLength, "get", len(s.VoteAddr)) + } + wrapper.VoteAddr = common.Bytes2Hex(s.VoteAddr) + + return json.Marshal(wrapper) +} + +func (s *SlashIndicatorFinalityEvidence) UnmarshalJSON(data []byte) error { + var wrapper = &types.SlashIndicatorFinalityEvidenceWrapper{} + if err := json.Unmarshal(data, wrapper); err != nil { + log.Crit("failed to Unmarshal", "error", err) + } + + s.VoteA.FromWrapper(&wrapper.VoteA) + s.VoteB.FromWrapper(&wrapper.VoteB) + if len(wrapper.VoteAddr) != types.BLSPublicKeyLength*2 { + log.Crit("wrong length of VoteAddr", "wanted", types.BLSPublicKeyLength*2, "get", len(wrapper.VoteAddr)) + } + s.VoteAddr = common.Hex2Bytes(wrapper.VoteAddr) + + return nil +} diff --git a/cmd/maliciousvote-submit/json_encoding_helper_test.go b/cmd/maliciousvote-submit/json_encoding_helper_test.go new file mode 100644 index 0000000000..9b1585b6e4 --- /dev/null +++ b/cmd/maliciousvote-submit/json_encoding_helper_test.go @@ -0,0 +1,23 @@ +package main + +import ( + "os" + "testing" + + "github.com/ethereum/go-ethereum/log" +) + +func TestSlashIndicatorFinalityEvidenceEncoding(t *testing.T) { + log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + evidence := `{"VoteA":{"SrcNum":1234,"SrcHash":"36068b819f244d27b5411d975f9ffd6d18c6084b50fb5595104ffd9de561a9f8","TarNum":1234,"TarHash":"36068b819f244d27b5411d975f9ffd6d18c6084b50fb5595104ffd9de561a9f8","Sig":"893682ebf26440a06daaff5695945ee2012146268f800c217bad9906ac64dc46996cd435e3e829529aa0445b52530070893682ebf26440a06daaff5695945ee2012146268f800c217bad9906ac64dc46996cd435e3e829529aa0445b52530070"},"VoteB":{"SrcNum":1234,"SrcHash":"36068b819f244d27b5411d975f9ffd6d18c6084b50fb5595104ffd9de561a9f8","TarNum":1234,"TarHash":"36068b819f244d27b5411d975f9ffd6d18c6084b50fb5595104ffd9de561a9f8","Sig":"893682ebf26440a06daaff5695945ee2012146268f800c217bad9906ac64dc46996cd435e3e829529aa0445b52530070893682ebf26440a06daaff5695945ee2012146268f800c217bad9906ac64dc46996cd435e3e829529aa0445b52530070"},"VoteAddr":"893682ebf26440a06daaff5695945ee2012146268f800c217bad9906ac64dc46996cd435e3e829529aa0445b52530070"}` + + slashIndicatorFinalityEvidence := &SlashIndicatorFinalityEvidence{} + if err := slashIndicatorFinalityEvidence.UnmarshalJSON([]byte(evidence)); err != nil { + log.Crit("SlashIndicatorFinalityEvidence UnmarshalJSON failed") + } + if output, err := slashIndicatorFinalityEvidence.MarshalJSON(); err != nil { + log.Crit("SlashIndicatorFinalityEvidence MarshalJSON failed") + } else if string(output) != evidence { + log.Crit("SlashIndicatorFinalityEvidence UnmarshalJSON MarshalJSON mismatch", "output", string(output), "evidence", evidence) + } +} diff --git a/cmd/maliciousvote-submit/main.go b/cmd/maliciousvote-submit/main.go new file mode 100644 index 0000000000..c954f53211 --- /dev/null +++ b/cmd/maliciousvote-submit/main.go @@ -0,0 +1,140 @@ +// submit the evidence of malicious voting +package main + +import ( + "context" + "fmt" + "math/big" + "os" + "strings" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/systemcontracts" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/log" + "gopkg.in/urfave/cli.v1" +) + +var ( + // Git SHA1 commit hash of the release (set via linker flags) + gitCommit = "" + gitDate = "" + + app *cli.App + + senderFlag = cli.StringFlag{ + Name: "sender", + Usage: "raw private key in hex format without 0x prefix; check permission on your own", + } + nodeFlag = cli.StringFlag{ + Name: "node", + Usage: "rpc endpoint, http,https,ws,wss,ipc are supported", + } + chainIdFlag = cli.UintFlag{ + Name: "chainId", + Usage: "chainId, can get by eth_chainId", + } + evidenceFlag = cli.StringFlag{ + Name: "evidence", + Usage: "params for submitFinalityViolationEvidence in json format; string", + } +) + +func init() { + app = flags.NewApp(gitCommit, gitDate, "a tool for submitting the evidence of malicious voting") + app.Flags = []cli.Flag{ + senderFlag, + nodeFlag, + chainIdFlag, + evidenceFlag, + } + app.Action = submitMaliciousVotes + cli.CommandHelpTemplate = flags.AppHelpTemplate +} + +func submitMaliciousVotes(c *cli.Context) { + // get sender + senderRawKey := c.GlobalString(senderFlag.Name) + if senderRawKey == "" { + log.Crit("no sender specified (--sender)") + } + sender, err := crypto.HexToECDSA(senderRawKey) + if err != nil { + log.Crit("get sender failed", "error", err) + } else { + log.Info("get sender success") + } + + // connect to the given URL + nodeURL := c.GlobalString(nodeFlag.Name) + if nodeURL == "" { + log.Crit("no node specified (--node)") + } + client, err := ethclient.Dial(nodeURL) + if err != nil { + log.Crit("Error connecting to client", "nodeURL", nodeURL, "error", err) + } else { + // when nodeURL is type of http or https, err==nil not mean successfully connected + if !strings.HasPrefix(nodeURL, "http") { + log.Info("Successfully connected to client", "nodeURL", nodeURL) + } + } + + // get chainId + chainId := c.GlobalUint(chainIdFlag.Name) + if chainId == 0 { + log.Crit("no chainId specified (--chainId)") + } else { + log.Info("get chainId success", "chainId", chainId) + } + + // get evidence + evidenceJson := c.GlobalString(evidenceFlag.Name) + if evidenceJson == "" { + log.Crit("no evidence specified (--evidence)") + } + var evidence SlashIndicatorFinalityEvidence + if err = evidence.UnmarshalJSON([]byte(evidenceJson)); err != nil { + log.Crit("Error parsing evidence", "error", err) + } else { + log.Info("get evidence success") + } + + ops, _ := bind.NewKeyedTransactorWithChainID(sender, big.NewInt(int64(chainId))) + //ops.GasLimit = 800000 + slashIndicator, _ := NewSlashIndicator(common.HexToAddress(systemcontracts.SlashContract), client) + tx, err := slashIndicator.SubmitFinalityViolationEvidence(ops, evidence) + if err != nil { + log.Crit("submitMaliciousVotes:", "error", err) + } + var rc *types.Receipt + for i := 0; i < 180; i++ { + rc, err = client.TransactionReceipt(context.Background(), tx.Hash()) + if err == nil && rc.Status != 0 { + log.Info("submitMaliciousVotes: submit evidence success", "receipt", rc) + break + } + if rc != nil && rc.Status == 0 { + log.Crit("submitMaliciousVotes: tx failed: ", "error", err, "receipt", rc) + } + time.Sleep(100 * time.Millisecond) + } + if rc == nil { + log.Crit("submitMaliciousVotes: submit evidence failed") + } +} + +func main() { + log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + +} diff --git a/cmd/maliciousvote-submit/slash_indicator.go b/cmd/maliciousvote-submit/slash_indicator.go new file mode 100644 index 0000000000..15f3a81a77 --- /dev/null +++ b/cmd/maliciousvote-submit/slash_indicator.go @@ -0,0 +1,217 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package main + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// SlashIndicatorFinalityEvidence is an auto generated low-level Go binding around an user-defined struct. +type SlashIndicatorFinalityEvidence struct { + VoteA SlashIndicatorVoteData + VoteB SlashIndicatorVoteData + VoteAddr []byte +} + +// SlashIndicatorVoteData is an auto generated low-level Go binding around an user-defined struct. +type SlashIndicatorVoteData struct { + SrcNum *big.Int + SrcHash [32]byte + TarNum *big.Int + TarHash [32]byte + Sig []byte +} + +// SlashIndicatorMetaData contains all meta data concerning the SlashIndicator contract. +var SlashIndicatorMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"srcNum\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"srcHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"tarNum\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"tarHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"sig\",\"type\":\"bytes\"}],\"internalType\":\"structSlashIndicator.VoteData\",\"name\":\"voteA\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"srcNum\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"srcHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"tarNum\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"tarHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"sig\",\"type\":\"bytes\"}],\"internalType\":\"structSlashIndicator.VoteData\",\"name\":\"voteB\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"voteAddr\",\"type\":\"bytes\"}],\"internalType\":\"structSlashIndicator.FinalityEvidence\",\"name\":\"_evidence\",\"type\":\"tuple\"}],\"name\":\"submitFinalityViolationEvidence\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", +} + +// SlashIndicatorABI is the input ABI used to generate the binding from. +// Deprecated: Use SlashIndicatorMetaData.ABI instead. +var SlashIndicatorABI = SlashIndicatorMetaData.ABI + +// SlashIndicator is an auto generated Go binding around an Ethereum contract. +type SlashIndicator struct { + SlashIndicatorCaller // Read-only binding to the contract + SlashIndicatorTransactor // Write-only binding to the contract + SlashIndicatorFilterer // Log filterer for contract events +} + +// SlashIndicatorCaller is an auto generated read-only Go binding around an Ethereum contract. +type SlashIndicatorCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SlashIndicatorTransactor is an auto generated write-only Go binding around an Ethereum contract. +type SlashIndicatorTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SlashIndicatorFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type SlashIndicatorFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SlashIndicatorSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type SlashIndicatorSession struct { + Contract *SlashIndicator // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SlashIndicatorCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type SlashIndicatorCallerSession struct { + Contract *SlashIndicatorCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// SlashIndicatorTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type SlashIndicatorTransactorSession struct { + Contract *SlashIndicatorTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SlashIndicatorRaw is an auto generated low-level Go binding around an Ethereum contract. +type SlashIndicatorRaw struct { + Contract *SlashIndicator // Generic contract binding to access the raw methods on +} + +// SlashIndicatorCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type SlashIndicatorCallerRaw struct { + Contract *SlashIndicatorCaller // Generic read-only contract binding to access the raw methods on +} + +// SlashIndicatorTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type SlashIndicatorTransactorRaw struct { + Contract *SlashIndicatorTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewSlashIndicator creates a new instance of SlashIndicator, bound to a specific deployed contract. +func NewSlashIndicator(address common.Address, backend bind.ContractBackend) (*SlashIndicator, error) { + contract, err := bindSlashIndicator(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &SlashIndicator{SlashIndicatorCaller: SlashIndicatorCaller{contract: contract}, SlashIndicatorTransactor: SlashIndicatorTransactor{contract: contract}, SlashIndicatorFilterer: SlashIndicatorFilterer{contract: contract}}, nil +} + +// NewSlashIndicatorCaller creates a new read-only instance of SlashIndicator, bound to a specific deployed contract. +func NewSlashIndicatorCaller(address common.Address, caller bind.ContractCaller) (*SlashIndicatorCaller, error) { + contract, err := bindSlashIndicator(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &SlashIndicatorCaller{contract: contract}, nil +} + +// NewSlashIndicatorTransactor creates a new write-only instance of SlashIndicator, bound to a specific deployed contract. +func NewSlashIndicatorTransactor(address common.Address, transactor bind.ContractTransactor) (*SlashIndicatorTransactor, error) { + contract, err := bindSlashIndicator(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &SlashIndicatorTransactor{contract: contract}, nil +} + +// NewSlashIndicatorFilterer creates a new log filterer instance of SlashIndicator, bound to a specific deployed contract. +func NewSlashIndicatorFilterer(address common.Address, filterer bind.ContractFilterer) (*SlashIndicatorFilterer, error) { + contract, err := bindSlashIndicator(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &SlashIndicatorFilterer{contract: contract}, nil +} + +// bindSlashIndicator binds a generic wrapper to an already deployed contract. +func bindSlashIndicator(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(SlashIndicatorABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_SlashIndicator *SlashIndicatorRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SlashIndicator.Contract.SlashIndicatorCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_SlashIndicator *SlashIndicatorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SlashIndicator.Contract.SlashIndicatorTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SlashIndicator *SlashIndicatorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SlashIndicator.Contract.SlashIndicatorTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_SlashIndicator *SlashIndicatorCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SlashIndicator.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_SlashIndicator *SlashIndicatorTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SlashIndicator.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SlashIndicator *SlashIndicatorTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SlashIndicator.Contract.contract.Transact(opts, method, params...) +} + +// SubmitFinalityViolationEvidence is a paid mutator transaction binding the contract method 0xcc844b73. +// +// Solidity: function submitFinalityViolationEvidence(((uint256,bytes32,uint256,bytes32,bytes),(uint256,bytes32,uint256,bytes32,bytes),bytes) _evidence) returns() +func (_SlashIndicator *SlashIndicatorTransactor) SubmitFinalityViolationEvidence(opts *bind.TransactOpts, _evidence SlashIndicatorFinalityEvidence) (*types.Transaction, error) { + return _SlashIndicator.contract.Transact(opts, "submitFinalityViolationEvidence", _evidence) +} + +// SubmitFinalityViolationEvidence is a paid mutator transaction binding the contract method 0xcc844b73. +// +// Solidity: function submitFinalityViolationEvidence(((uint256,bytes32,uint256,bytes32,bytes),(uint256,bytes32,uint256,bytes32,bytes),bytes) _evidence) returns() +func (_SlashIndicator *SlashIndicatorSession) SubmitFinalityViolationEvidence(_evidence SlashIndicatorFinalityEvidence) (*types.Transaction, error) { + return _SlashIndicator.Contract.SubmitFinalityViolationEvidence(&_SlashIndicator.TransactOpts, _evidence) +} + +// SubmitFinalityViolationEvidence is a paid mutator transaction binding the contract method 0xcc844b73. +// +// Solidity: function submitFinalityViolationEvidence(((uint256,bytes32,uint256,bytes32,bytes),(uint256,bytes32,uint256,bytes32,bytes),bytes) _evidence) returns() +func (_SlashIndicator *SlashIndicatorTransactorSession) SubmitFinalityViolationEvidence(_evidence SlashIndicatorFinalityEvidence) (*types.Transaction, error) { + return _SlashIndicator.Contract.SubmitFinalityViolationEvidence(&_SlashIndicator.TransactOpts, _evidence) +} diff --git a/core/monitor/malicious_vote_monitor.go b/core/monitor/malicious_vote_monitor.go index bdf2a3c4ab..be8288a4b2 100644 --- a/core/monitor/malicious_vote_monitor.go +++ b/core/monitor/malicious_vote_monitor.go @@ -1,7 +1,8 @@ package monitor import ( - "github.com/ethereum/go-ethereum/common" + "encoding/json" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -59,27 +60,37 @@ func (m *MaliciousVoteMonitor) ConflictDetect(newVote *types.VoteEnvelope, pendi } for ; blockNumber <= pendingBlockNumber+upperLimitOfVoteBlockNumber; blockNumber++ { if voteDataBuffer.Contains(blockNumber) { - voteData, ok := voteDataBuffer.Get(blockNumber) + voteEnvelope, ok := voteDataBuffer.Get(blockNumber) if !ok { log.Error("Failed to get voteData info from LRU cache.") continue } + maliciousVote := false if blockNumber == targetNumber { - log.Warn("violate rule1", "VoteAddress", common.Bytes2Hex(newVote.VoteAddress[:]), "voteExisted", voteData.(*types.VoteData), "newVote", newVote.Data) violateRule1Counter.Inc(1) - // prepare message for slashing - return true - } else if (blockNumber < targetNumber && voteData.(*types.VoteData).SourceNumber > sourceNumber) || - (blockNumber > targetNumber && voteData.(*types.VoteData).SourceNumber < sourceNumber) { - log.Warn("violate rule2", "VoteAddress", common.Bytes2Hex(newVote.VoteAddress[:]), "voteExisted", voteData.(*types.VoteData), "newVote", newVote.Data) + maliciousVote = true + } else if (blockNumber < targetNumber && voteEnvelope.(*types.VoteEnvelope).Data.SourceNumber > sourceNumber) || + (blockNumber > targetNumber && voteEnvelope.(*types.VoteEnvelope).Data.SourceNumber < sourceNumber) { violateRule2Counter.Inc(1) - // prepare message for slashing + maliciousVote = true + } + if maliciousVote { + evidence := types.NewSlashIndicatorFinalityEvidenceWrapper(voteEnvelope.(*types.VoteEnvelope), newVote) + if evidence != nil { + if evidenceJson, err := json.Marshal(evidence); err == nil { + log.Warn("MaliciousVote", "evidence", string(evidenceJson)) + } else { + log.Warn("MaliciousVote, Marshal evidence failed") + } + } else { + log.Warn("MaliciousVote, construct evidence failed") + } return true } } } // for simplicity, Just override even if the targetNumber has existed. - voteDataBuffer.Add(newVote.Data.TargetNumber, newVote.Data) + voteDataBuffer.Add(newVote.Data.TargetNumber, newVote) return false } diff --git a/core/types/vote.go b/core/types/vote.go index ce8ab9a0eb..5dee69dc61 100644 --- a/core/types/vote.go +++ b/core/types/vote.go @@ -1,6 +1,8 @@ package types import ( + "bytes" + "math/big" "sync/atomic" "github.com/pkg/errors" @@ -90,3 +92,41 @@ func (vote *VoteEnvelope) Verify() error { } return nil } + +type SlashIndicatorVoteDataWrapper struct { + SrcNum *big.Int + SrcHash string + TarNum *big.Int + TarHash string + Sig string +} + +type SlashIndicatorFinalityEvidenceWrapper struct { + VoteA SlashIndicatorVoteDataWrapper + VoteB SlashIndicatorVoteDataWrapper + VoteAddr string +} + +func NewSlashIndicatorFinalityEvidenceWrapper(vote1, vote2 *VoteEnvelope) *SlashIndicatorFinalityEvidenceWrapper { + if !bytes.Equal(vote1.VoteAddress[:], vote1.VoteAddress[:]) || + vote1.Data == nil || vote2.Data == nil { + return nil + } + return &SlashIndicatorFinalityEvidenceWrapper{ + VoteA: SlashIndicatorVoteDataWrapper{ + SrcNum: big.NewInt(int64(vote1.Data.SourceNumber)), + SrcHash: common.Bytes2Hex(vote1.Data.SourceHash[:]), + TarNum: big.NewInt(int64(vote1.Data.TargetNumber)), + TarHash: common.Bytes2Hex(vote1.Data.TargetHash[:]), + Sig: common.Bytes2Hex(vote1.Signature[:]), + }, + VoteB: SlashIndicatorVoteDataWrapper{ + SrcNum: big.NewInt(int64(vote2.Data.SourceNumber)), + SrcHash: common.Bytes2Hex(vote2.Data.SourceHash[:]), + TarNum: big.NewInt(int64(vote2.Data.TargetNumber)), + TarHash: common.Bytes2Hex(vote2.Data.TargetHash[:]), + Sig: common.Bytes2Hex(vote2.Signature[:]), + }, + VoteAddr: common.Bytes2Hex(vote1.VoteAddress[:]), + } +}