mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-30 13:11:38 +00:00
implement broadcast_duplicate_vote endpoint
This commit is contained in:
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/tendermint/tendermint/abci/types"
|
"github.com/tendermint/tendermint/abci/types"
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
dbm "github.com/tendermint/tendermint/libs/db"
|
dbm "github.com/tendermint/tendermint/libs/db"
|
||||||
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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)}
|
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
|
// tx is either "key=value" or just arbitrary bytes
|
||||||
func (app *KVStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx {
|
func (app *KVStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx {
|
||||||
var key, value []byte
|
var key, value []byte
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
amino "github.com/tendermint/go-amino"
|
amino "github.com/tendermint/go-amino"
|
||||||
|
"github.com/tendermint/tendermint/crypto"
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
||||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
@ -229,6 +230,15 @@ func (c *HTTP) Validators(height *int64) (*ctypes.ResultValidators, error) {
|
|||||||
return result, nil
|
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... **/
|
/** websocket event stuff here... **/
|
||||||
|
|
||||||
type WSEvents struct {
|
type WSEvents struct {
|
||||||
|
@ -21,6 +21,7 @@ implementation.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/tendermint/tendermint/crypto"
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
"github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/types"
|
||||||
@ -51,6 +52,7 @@ type SignClient interface {
|
|||||||
Validators(height *int64) (*ctypes.ResultValidators, error)
|
Validators(height *int64) (*ctypes.ResultValidators, error)
|
||||||
Tx(hash []byte, prove bool) (*ctypes.ResultTx, error)
|
Tx(hash []byte, prove bool) (*ctypes.ResultTx, error)
|
||||||
TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, 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.
|
// HistoryClient shows us data from genesis to now in large chunks.
|
||||||
|
@ -3,6 +3,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/crypto"
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
||||||
nm "github.com/tendermint/tendermint/node"
|
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)
|
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 {
|
func (c *Local) Subscribe(ctx context.Context, subscriber string, query tmpubsub.Query, out chan<- interface{}) error {
|
||||||
return c.EventBus.Subscribe(ctx, subscriber, query, out)
|
return c.EventBus.Subscribe(ctx, subscriber, query, out)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package client_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ import (
|
|||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/privval"
|
||||||
"github.com/tendermint/tendermint/rpc/client"
|
"github.com/tendermint/tendermint/rpc/client"
|
||||||
rpctest "github.com/tendermint/tendermint/rpc/test"
|
rpctest "github.com/tendermint/tendermint/rpc/test"
|
||||||
"github.com/tendermint/tendermint/types"
|
"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"),
|
"unconfirmed_txs": rpc.NewRPCFunc(UnconfirmedTxs, "limit"),
|
||||||
"num_unconfirmed_txs": rpc.NewRPCFunc(NumUnconfirmedTxs, ""),
|
"num_unconfirmed_txs": rpc.NewRPCFunc(NumUnconfirmedTxs, ""),
|
||||||
|
|
||||||
// broadcast API
|
// tx broadcast API
|
||||||
"broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommit, "tx"),
|
"broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommit, "tx"),
|
||||||
"broadcast_tx_sync": rpc.NewRPCFunc(BroadcastTxSync, "tx"),
|
"broadcast_tx_sync": rpc.NewRPCFunc(BroadcastTxSync, "tx"),
|
||||||
"broadcast_tx_async": rpc.NewRPCFunc(BroadcastTxAsync, "tx"),
|
"broadcast_tx_async": rpc.NewRPCFunc(BroadcastTxAsync, "tx"),
|
||||||
@ -38,6 +38,9 @@ var Routes = map[string]*rpc.RPCFunc{
|
|||||||
// abci API
|
// abci API
|
||||||
"abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,height,prove"),
|
"abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,height,prove"),
|
||||||
"abci_info": rpc.NewRPCFunc(ABCIInfo, ""),
|
"abci_info": rpc.NewRPCFunc(ABCIInfo, ""),
|
||||||
|
|
||||||
|
// evidence API
|
||||||
|
"broadcast_duplicate_vote": rpc.NewRPCFunc(BroadcastDuplicateVote, "pubkey,vote1,vote2"),
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddUnsafeRoutes() {
|
func AddUnsafeRoutes() {
|
||||||
|
@ -191,6 +191,11 @@ type ResultABCIQuery struct {
|
|||||||
Response abci.ResponseQuery `json:"response"`
|
Response abci.ResponseQuery `json:"response"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Result of broadcasting evidence
|
||||||
|
type ResultBroadcastDuplicateVote struct {
|
||||||
|
Hash []byte `json:"hash"`
|
||||||
|
}
|
||||||
|
|
||||||
// empty results
|
// empty results
|
||||||
type (
|
type (
|
||||||
ResultUnsafeFlushMempool struct{}
|
ResultUnsafeFlushMempool struct{}
|
||||||
|
@ -65,6 +65,7 @@ type Evidence interface {
|
|||||||
func RegisterEvidences(cdc *amino.Codec) {
|
func RegisterEvidences(cdc *amino.Codec) {
|
||||||
cdc.RegisterInterface((*Evidence)(nil), nil)
|
cdc.RegisterInterface((*Evidence)(nil), nil)
|
||||||
cdc.RegisterConcrete(&DuplicateVoteEvidence{}, "tendermint/DuplicateVoteEvidence", nil)
|
cdc.RegisterConcrete(&DuplicateVoteEvidence{}, "tendermint/DuplicateVoteEvidence", nil)
|
||||||
|
cdc.RegisterConcrete(&MockGoodEvidence{}, "tendermint/MockGoodEvidence", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterMockEvidences(cdc *amino.Codec) {
|
func RegisterMockEvidences(cdc *amino.Codec) {
|
||||||
|
Reference in New Issue
Block a user