Compare commits

...

10 Commits

Author SHA1 Message Date
mossid
f89daec13e address comments in progress 2019-02-08 21:48:41 +01:00
mossid
29a7bea364 Merge branch 'joon/2252-evidence-rpc' of github.com:mossid/tendermint into joon/2252-evidence-rpc 2019-02-08 20:33:36 +01:00
mossid
c4f1b7560d Merge branch 'develop' into joon/2252-evidence-rpc 2019-02-08 20:32:47 +01:00
Anton Kaliaev
0e0e0e8b5a Update rpc/client/main_test.go
Co-Authored-By: mossid <torecursedivine@gmail.com>
2019-02-08 11:26:55 -08:00
Anton Kaliaev
44be0393b5 Update abci/example/kvstore/persistent_kvstore.go
Co-Authored-By: mossid <torecursedivine@gmail.com>
2019-02-08 11:26:38 -08:00
Ethan Buchman
0d7f17ce57 Merge branch 'develop' into joon/2252-evidence-rpc 2018-12-15 18:58:14 -05:00
mossid
1b978c0e9a address comments 2018-11-06 18:55:34 +01:00
mossid
2420536bfa address comments 2018-11-06 18:11:10 +01:00
mossid
1a339fdd6e fix test_cover 2018-10-13 20:08:03 +09:00
mossid
93e2350270 implement broadcast_duplicate_vote endpoint 2018-10-13 18:58:32 +09:00
11 changed files with 262 additions and 6 deletions

View File

@@ -9,8 +9,10 @@ import (
"github.com/tendermint/tendermint/abci/example/code"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types"
)
const (
@@ -27,6 +29,8 @@ type PersistentKVStoreApplication struct {
// validator set
ValUpdates []types.ValidatorUpdate
valAddrToPubKeyMap map[string]types.PubKey
logger log.Logger
}
@@ -40,8 +44,9 @@ func NewPersistentKVStoreApplication(dbDir string) *PersistentKVStoreApplication
state := loadState(db)
return &PersistentKVStoreApplication{
app: &KVStoreApplication{state: state},
logger: log.NewNopLogger(),
app: &KVStoreApplication{state: state},
valAddrToPubKeyMap: make(map[string]types.PubKey),
logger: log.NewNopLogger(),
}
}
@@ -83,8 +88,18 @@ func (app *PersistentKVStoreApplication) Commit() types.ResponseCommit {
return app.app.Commit()
}
func (app *PersistentKVStoreApplication) Query(reqQuery types.RequestQuery) types.ResponseQuery {
return app.app.Query(reqQuery)
func (app *PersistentKVStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
switch reqQuery.Path {
case "/val":
key := []byte("val:" + string(reqQuery.Data))
value := app.app.state.db.Get(key)
resQuery.Key = reqQuery.Data
resQuery.Value = value
return
default:
return app.app.Query(reqQuery)
}
}
// Save the validators in the merkle tree
@@ -102,6 +117,20 @@ func (app *PersistentKVStoreApplication) InitChain(req types.RequestInitChain) t
func (app *PersistentKVStoreApplication) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock {
// reset valset changes
app.ValUpdates = make([]types.ValidatorUpdate, 0)
for _, ev := range req.ByzantineValidators {
switch ev.Type {
case tmtypes.ABCIEvidenceTypeDuplicateVote:
// decrease voting power by 1
if ev.TotalVotingPower == 0 {
continue
}
app.updateValidator(types.ValidatorUpdate{
PubKey: app.valAddrToPubKeyMap[string(ev.Validator.Address)],
Power: ev.TotalVotingPower - 1,
})
}
}
return types.ResponseBeginBlock{}
}
@@ -173,6 +202,10 @@ func (app *PersistentKVStoreApplication) execValidatorTx(tx []byte) types.Respon
// add, update, or remove a validator
func (app *PersistentKVStoreApplication) updateValidator(v types.ValidatorUpdate) types.ResponseDeliverTx {
key := []byte("val:" + string(v.PubKey.Data))
pubkey := ed25519.PubKeyEd25519{}
copy(pubkey[:], v.PubKey.Data)
if v.Power == 0 {
// remove validator
if !app.app.state.db.Has(key) {
@@ -181,6 +214,9 @@ func (app *PersistentKVStoreApplication) updateValidator(v types.ValidatorUpdate
Log: fmt.Sprintf("Cannot remove non-existent validator %X", key)}
}
app.app.state.db.Delete(key)
delete(app.valAddrToPubKeyMap, string(pubkey.Address()))
} else {
// add or update validator
value := bytes.NewBuffer(make([]byte, 0))
@@ -190,6 +226,8 @@ func (app *PersistentKVStoreApplication) updateValidator(v types.ValidatorUpdate
Log: fmt.Sprintf("Error encoding validator: %v", err)}
}
app.app.state.db.Set(key, value.Bytes())
app.valAddrToPubKeyMap[string(pubkey.Address())] = v.PubKey
}
// we only update the changes array if we successfully updated the tree

View File

@@ -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"
@@ -247,6 +248,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 {

View File

@@ -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"
@@ -74,6 +75,7 @@ type Client interface {
HistoryClient
StatusClient
EventsClient
EvidenceClient
}
// NetworkClient is general info about the network state. May not
@@ -99,3 +101,8 @@ type MempoolClient interface {
UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error)
NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error)
}
// EvidenceClient is used for submitting evidence for malicious behaviours
type EvidenceClient interface {
BroadcastDuplicateVote(pubkey crypto.PubKey, vote1 types.Vote, vote2 types.Vote) (*ctypes.ResultBroadcastDuplicateVote, error)
}

View File

@@ -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"
@@ -140,6 +141,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)
}

View File

@@ -1,6 +1,8 @@
package client_test
import (
"fmt"
"io/ioutil"
"os"
"testing"
@@ -13,7 +15,12 @@ var node *nm.Node
func TestMain(m *testing.M) {
// start a tendermint node (and kvstore) in the background to test against
app := kvstore.NewKVStoreApplication()
dir, err := ioutil.TempDir("/tmp", "rpc-client-test")
if err != nil {
m.Fatal(err)
os.Exit(1)
}
app := kvstore.NewPersistentKVStoreApplication(dir)
node = rpctest.StartTendermint(app)
code := m.Run()

View File

@@ -35,6 +35,7 @@ type Client struct {
client.HistoryClient
client.StatusClient
client.EventsClient
client.EvidenceClient
cmn.Service
}
@@ -134,3 +135,7 @@ func (c Client) Commit(height *int64) (*ctypes.ResultCommit, error) {
func (c Client) Validators(height *int64) (*ctypes.ResultValidators, error) {
return core.Validators(height)
}
func (c Client) BroadcastEvidence(pubkey crypto.PubKey, vote1, vote2 types.Vote) (*ctypes.ResultBroadcastDuplicateVote, error) {
return core.BroadcastDuplicateVote(pubkey, vote1, vote2)
}

View File

@@ -1,7 +1,9 @@
package client_test
import (
"bytes"
"fmt"
"math/rand"
"net/http"
"strings"
"testing"
@@ -11,6 +13,8 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/privval"
"github.com/tendermint/tendermint/rpc/client"
rpctest "github.com/tendermint/tendermint/rpc/test"
"github.com/tendermint/tendermint/types"
@@ -435,3 +439,137 @@ func TestTxSearch(t *testing.T) {
require.Len(t, result.Txs, 0)
}
}
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, vote *types.Vote, vote2 *types.Vote, chainID string) types.DuplicateVoteEvidence {
var err error
vote2_ := deepcpVote(vote2)
vote2_.Signature, err = val.PrivKey.Sign(vote2_.SignBytes(chainID))
require.NoError(t, err)
return types.DuplicateVoteEvidence{
PubKey: val.PubKey,
VoteA: vote,
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{},
},
}
var err error
vote.Signature, err = val.PrivKey.Sign(vote.SignBytes(chainID))
require.NoError(t, err)
vote2 := deepcpVote(vote)
vote2.BlockID.Hash = []byte{0x01}
ev = newEvidence(t, val, vote, vote2, chainID)
fakes = make([]types.DuplicateVoteEvidence, 42)
// 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)
// exactly same vote
vote2 = deepcpVote(vote)
fakes[41] = 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)
t.Logf("evidence %v", ev)
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")
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, result %+v", result)
ed25519pub := ev.PubKey.(ed25519.PubKeyEd25519)
rawpub := ed25519pub[:]
result2, err := c.ABCIQuery("/val", rawpub)
require.Nil(t, err, "Error querying evidence, err %v", err)
qres := result2.Response
require.True(t, qres.IsOK(), "Response not OK")
var v abci.ValidatorUpdate
err = abci.ReadMessage(bytes.NewReader(qres.Value), &v)
require.NoError(t, err, "Error reading query result, value %v", qres.Value)
require.EqualValues(t, rawpub, v.PubKey.Data, "Stored PubKey not equal with expected, value %v", string(qres.Value))
require.Equal(t, int64(9), v.Power, "Stored Power not equal with expected, value %v", string(qres.Value))
for _, fake := range fakes {
_, err := c.BroadcastDuplicateVote(fake.PubKey, *fake.VoteA, *fake.VoteB)
require.Error(t, err, "Broadcasting fake evidence succeed: %s", fake.String())
require.True(t, strings.HasPrefix(err.Error(), "Error broadcasting evidence, adding evidence"), "Broadcasting fake evidence failed on HTTP call: %s", 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)
}

26
rpc/core/evidence.go Normal file
View File

@@ -0,0 +1,26 @@
package core
import (
"fmt"
"github.com/tendermint/tendermint/crypto"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/types"
)
// ### Broadcast Duplicate Vote 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) {
ev := &types.DuplicateVoteEvidence{pubkey, &vote1, &vote2}
err := evidencePool.AddEvidence(ev)
if err != nil {
return nil, fmt.Errorf("Error broadcasting evidence, adding 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"),
"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() {

View File

@@ -193,6 +193,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{}