implement broadcast_duplicate_vote endpoint

This commit is contained in:
mossid
2018-10-13 18:58:32 +09:00
parent 69ecda18f9
commit 93e2350270
10 changed files with 209 additions and 1 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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.

View File

@ -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)
} }

View File

@ -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
View 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
View 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
}

View File

@ -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() {

View File

@ -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{}

View File

@ -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) {