mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-29 20:51:45 +00:00
implement broadcast_duplicate_vote endpoint
This commit is contained in:
@ -10,6 +10,7 @@ import (
|
||||
"github.com/tendermint/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -68,6 +69,23 @@ func (app *KVStoreApplication) Info(req types.RequestInfo) (resInfo types.Respon
|
||||
return types.ResponseInfo{Data: fmt.Sprintf("{\"size\":%v}", app.state.Size)}
|
||||
}
|
||||
|
||||
// handle evidence
|
||||
func (app *KVStoreApplication) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock {
|
||||
for _, evidence := range req.ByzantineValidators {
|
||||
switch evidence.Type {
|
||||
case tmtypes.ABCIEvidenceTypeDuplicateVote:
|
||||
key := prefixKey(evidence.Validator.Address)
|
||||
// Do nothing, just store it in the state to check from outside
|
||||
app.state.db.Set(key, []byte{})
|
||||
app.state.Size += 1
|
||||
}
|
||||
}
|
||||
|
||||
return types.ResponseBeginBlock{
|
||||
Tags: []cmn.KVPair{{Key: []byte("height"), Value: []byte(fmt.Sprintf("%d", req.Header.Height))}},
|
||||
}
|
||||
}
|
||||
|
||||
// tx is either "key=value" or just arbitrary bytes
|
||||
func (app *KVStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx {
|
||||
var key, value []byte
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
@ -229,6 +230,15 @@ func (c *HTTP) Validators(height *int64) (*ctypes.ResultValidators, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *HTTP) BroadcastDuplicateVote(pubkey crypto.PubKey, vote1 types.Vote, vote2 types.Vote) (*ctypes.ResultBroadcastDuplicateVote, error) {
|
||||
result := new(ctypes.ResultBroadcastDuplicateVote)
|
||||
_, err := c.rpc.Call("broadcast_duplicate_vote", map[string]interface{}{"pubkey": pubkey, "vote1": vote1, "vote2": vote2}, result)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "BroadcastDuplicateVote")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
/** websocket event stuff here... **/
|
||||
|
||||
type WSEvents struct {
|
||||
|
@ -21,6 +21,7 @@ implementation.
|
||||
*/
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
@ -51,6 +52,7 @@ type SignClient interface {
|
||||
Validators(height *int64) (*ctypes.ResultValidators, error)
|
||||
Tx(hash []byte, prove bool) (*ctypes.ResultTx, error)
|
||||
TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error)
|
||||
BroadcastDuplicateVote(pubkey crypto.PubKey, vote1 types.Vote, vote2 types.Vote) (*ctypes.ResultBroadcastDuplicateVote, error)
|
||||
}
|
||||
|
||||
// HistoryClient shows us data from genesis to now in large chunks.
|
||||
|
@ -3,6 +3,7 @@ package client
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
@ -132,6 +133,10 @@ func (Local) TxSearch(query string, prove bool, page, perPage int) (*ctypes.Resu
|
||||
return core.TxSearch(query, prove, page, perPage)
|
||||
}
|
||||
|
||||
func (Local) BroadcastDuplicateVote(pubkey crypto.PubKey, vote1 types.Vote, vote2 types.Vote) (*ctypes.ResultBroadcastDuplicateVote, error) {
|
||||
return core.BroadcastDuplicateVote(pubkey, vote1, vote2)
|
||||
}
|
||||
|
||||
func (c *Local) Subscribe(ctx context.Context, subscriber string, query tmpubsub.Query, out chan<- interface{}) error {
|
||||
return c.EventBus.Subscribe(ctx, subscriber, query, out)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package client_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -10,6 +11,7 @@ import (
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"github.com/tendermint/tendermint/privval"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
rpctest "github.com/tendermint/tendermint/rpc/test"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
@ -371,3 +373,123 @@ func TestTxSearch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deepcpVote(vote *types.Vote) (res *types.Vote) {
|
||||
res = &types.Vote{
|
||||
ValidatorAddress: make([]byte, len(vote.ValidatorAddress)),
|
||||
ValidatorIndex: vote.ValidatorIndex,
|
||||
Height: vote.Height,
|
||||
Round: vote.Round,
|
||||
Type: vote.Type,
|
||||
BlockID: types.BlockID{
|
||||
Hash: make([]byte, len(vote.BlockID.Hash)),
|
||||
PartsHeader: vote.BlockID.PartsHeader,
|
||||
},
|
||||
Signature: make([]byte, len(vote.Signature)),
|
||||
}
|
||||
copy(res.ValidatorAddress, vote.ValidatorAddress)
|
||||
copy(res.BlockID.Hash, vote.BlockID.Hash)
|
||||
copy(res.Signature, vote.Signature)
|
||||
return
|
||||
}
|
||||
|
||||
func newEvidence(t *testing.T, val *privval.FilePV, vote1 *types.Vote, vote2 *types.Vote, chainID string) types.DuplicateVoteEvidence {
|
||||
var err error
|
||||
vote1_ := deepcpVote(vote1)
|
||||
vote1_.Signature, err = val.PrivKey.Sign(vote1_.SignBytes(chainID))
|
||||
require.NoError(t, err)
|
||||
vote2_ := deepcpVote(vote2)
|
||||
vote2_.Signature, err = val.PrivKey.Sign(vote2_.SignBytes(chainID))
|
||||
require.NoError(t, err)
|
||||
|
||||
return types.DuplicateVoteEvidence{
|
||||
PubKey: val.PubKey,
|
||||
VoteA: vote1_,
|
||||
VoteB: vote2_,
|
||||
}
|
||||
}
|
||||
|
||||
func makeEvidences(t *testing.T, val *privval.FilePV, chainID string) (ev types.DuplicateVoteEvidence, fakes []types.DuplicateVoteEvidence) {
|
||||
vote := &types.Vote{
|
||||
ValidatorAddress: val.Address,
|
||||
ValidatorIndex: 0,
|
||||
Height: 1,
|
||||
Round: 0,
|
||||
Type: types.VoteTypePrevote,
|
||||
BlockID: types.BlockID{
|
||||
Hash: []byte{0x00},
|
||||
PartsHeader: types.PartSetHeader{},
|
||||
},
|
||||
}
|
||||
|
||||
vote2 := deepcpVote(vote)
|
||||
vote2.BlockID.Hash = []byte{0x01}
|
||||
|
||||
ev = newEvidence(t, val, vote, vote2, chainID)
|
||||
|
||||
fakes = make([]types.DuplicateVoteEvidence, 41)
|
||||
|
||||
// different address
|
||||
vote2 = deepcpVote(vote)
|
||||
for i := 0; i < 10; i++ {
|
||||
rand.Read(vote2.ValidatorAddress)
|
||||
fakes[i] = newEvidence(t, val, vote, vote2, chainID)
|
||||
}
|
||||
// different index
|
||||
vote2 = deepcpVote(vote)
|
||||
for i := 10; i < 20; i++ {
|
||||
vote2.ValidatorIndex = rand.Int()%100 + 1
|
||||
fakes[i] = newEvidence(t, val, vote, vote2, chainID)
|
||||
}
|
||||
// different height
|
||||
vote2 = deepcpVote(vote)
|
||||
for i := 20; i < 30; i++ {
|
||||
vote2.Height = rand.Int63()%1000 + 100
|
||||
fakes[i] = newEvidence(t, val, vote, vote2, chainID)
|
||||
}
|
||||
// different round
|
||||
vote2 = deepcpVote(vote)
|
||||
for i := 30; i < 40; i++ {
|
||||
vote2.Round = rand.Int()%10 + 1
|
||||
fakes[i] = newEvidence(t, val, vote, vote2, chainID)
|
||||
}
|
||||
// different type
|
||||
vote2 = deepcpVote(vote)
|
||||
vote2.Type = types.VoteTypePrecommit
|
||||
fakes[40] = newEvidence(t, val, vote, vote2, chainID)
|
||||
return
|
||||
}
|
||||
|
||||
func TestBroadcastDuplicateVote(t *testing.T) {
|
||||
config := rpctest.GetConfig()
|
||||
chainID := config.ChainID()
|
||||
pv := privval.LoadOrGenFilePV(config.PrivValidatorFile())
|
||||
|
||||
ev, fakes := makeEvidences(t, pv, chainID)
|
||||
|
||||
for i, c := range GetClients() {
|
||||
t.Logf("client %d", i)
|
||||
|
||||
result, err := c.BroadcastDuplicateVote(ev.PubKey, *ev.VoteA, *ev.VoteB)
|
||||
require.Nil(t, err, "Error broadcasting evidence, evidence %v", ev.String())
|
||||
|
||||
info, err := c.BlockchainInfo(0, 0)
|
||||
require.NoError(t, err)
|
||||
client.WaitForHeight(c, info.LastHeight+1, nil)
|
||||
|
||||
require.Equal(t, ev.Hash(), result.Hash, "Invalid response, evidence %v, result %+v", ev.String(), result)
|
||||
|
||||
result2, err := c.ABCIQuery("/key", ev.PubKey.Address())
|
||||
require.Nil(t, err, "Error querying evidence, evidence %v", ev.String())
|
||||
qres := result2.Response
|
||||
require.True(t, qres.IsOK(), "Response not OK, evidence %v", ev.String())
|
||||
|
||||
require.EqualValues(t, []byte{}, qres.Value, "Value not equal with expected, evidence %v, value %v", ev.String(), string(qres.Value))
|
||||
|
||||
for _, fake := range fakes {
|
||||
_, err := c.BroadcastDuplicateVote(fake.PubKey, *fake.VoteA, *fake.VoteB)
|
||||
require.Error(t, err, "Broadcasting fake evidence succeed, evidence %v", fake.String())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
12
rpc/client/wire.go
Normal file
12
rpc/client/wire.go
Normal file
@ -0,0 +1,12 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-amino"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
var cdc = amino.NewCodec()
|
||||
|
||||
func init() {
|
||||
types.RegisterEvidences(cdc)
|
||||
}
|
30
rpc/core/evidence.go
Normal file
30
rpc/core/evidence.go
Normal file
@ -0,0 +1,30 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// ### Query Parameters
|
||||
//
|
||||
// | Parameter | Type | Default | Required | Description |
|
||||
// |-----------+--------+---------+----------+-------------------------------|
|
||||
// | pubkey | PubKey | nil | true | PubKey of the byzantine actor |
|
||||
// | vote1 | Vote | nil | true | First vote |
|
||||
// | vote2 | Vote | nil | true | Second vote |
|
||||
func BroadcastDuplicateVote(pubkey crypto.PubKey, vote1 types.Vote, vote2 types.Vote) (*ctypes.ResultBroadcastDuplicateVote, error) {
|
||||
chainID := p2pTransport.NodeInfo().Network
|
||||
ev := &types.DuplicateVoteEvidence{pubkey, &vote1, &vote2}
|
||||
if err := vote1.Verify(chainID, pubkey); err != nil {
|
||||
return nil, fmt.Errorf("Error broadcasting evidence: %v", err)
|
||||
}
|
||||
|
||||
err := evidencePool.AddEvidence(ev)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error broadcasting evidence: %v", err)
|
||||
}
|
||||
return &ctypes.ResultBroadcastDuplicateVote{ev.Hash()}, nil
|
||||
}
|
@ -30,7 +30,7 @@ var Routes = map[string]*rpc.RPCFunc{
|
||||
"unconfirmed_txs": rpc.NewRPCFunc(UnconfirmedTxs, "limit"),
|
||||
"num_unconfirmed_txs": rpc.NewRPCFunc(NumUnconfirmedTxs, ""),
|
||||
|
||||
// broadcast API
|
||||
// tx broadcast API
|
||||
"broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommit, "tx"),
|
||||
"broadcast_tx_sync": rpc.NewRPCFunc(BroadcastTxSync, "tx"),
|
||||
"broadcast_tx_async": rpc.NewRPCFunc(BroadcastTxAsync, "tx"),
|
||||
@ -38,6 +38,9 @@ var Routes = map[string]*rpc.RPCFunc{
|
||||
// abci API
|
||||
"abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,height,prove"),
|
||||
"abci_info": rpc.NewRPCFunc(ABCIInfo, ""),
|
||||
|
||||
// evidence API
|
||||
"broadcast_duplicate_vote": rpc.NewRPCFunc(BroadcastDuplicateVote, "pubkey,vote1,vote2"),
|
||||
}
|
||||
|
||||
func AddUnsafeRoutes() {
|
||||
|
@ -191,6 +191,11 @@ type ResultABCIQuery struct {
|
||||
Response abci.ResponseQuery `json:"response"`
|
||||
}
|
||||
|
||||
// Result of broadcasting evidence
|
||||
type ResultBroadcastDuplicateVote struct {
|
||||
Hash []byte `json:"hash"`
|
||||
}
|
||||
|
||||
// empty results
|
||||
type (
|
||||
ResultUnsafeFlushMempool struct{}
|
||||
|
@ -65,6 +65,7 @@ type Evidence interface {
|
||||
func RegisterEvidences(cdc *amino.Codec) {
|
||||
cdc.RegisterInterface((*Evidence)(nil), nil)
|
||||
cdc.RegisterConcrete(&DuplicateVoteEvidence{}, "tendermint/DuplicateVoteEvidence", nil)
|
||||
cdc.RegisterConcrete(&MockGoodEvidence{}, "tendermint/MockGoodEvidence", nil)
|
||||
}
|
||||
|
||||
func RegisterMockEvidences(cdc *amino.Codec) {
|
||||
|
Reference in New Issue
Block a user