mirror of
https://github.com/fluencelabs/tendermint
synced 2025-07-24 08:41:59 +00:00
Compare commits
10 Commits
v0.0.1
...
joon/2252-
Author | SHA1 | Date | |
---|---|---|---|
|
f89daec13e | ||
|
29a7bea364 | ||
|
c4f1b7560d | ||
|
0e0e0e8b5a | ||
|
44be0393b5 | ||
|
0d7f17ce57 | ||
|
1b978c0e9a | ||
|
2420536bfa | ||
|
1a339fdd6e | ||
|
93e2350270 |
@@ -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
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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()
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
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)
|
||||
}
|
26
rpc/core/evidence.go
Normal file
26
rpc/core/evidence.go
Normal 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
|
||||
}
|
@@ -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() {
|
||||
|
@@ -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{}
|
||||
|
Reference in New Issue
Block a user