Skip to content

Commit

Permalink
rpc: /broadcast_evidence (#3481)
Browse files Browse the repository at this point in the history
* implement broadcast_duplicate_vote endpoint

* fix test_cover

* address comments

* address comments

* Update abci/example/kvstore/persistent_kvstore.go

Co-Authored-By: mossid <torecursedivine@gmail.com>

* Update rpc/client/main_test.go

Co-Authored-By: mossid <torecursedivine@gmail.com>

* address comments in progress

* reformat the code

* make linter happy

* make tests pass

* replace BroadcastDuplicateVote with BroadcastEvidence

* fix test

* fix endpoint name

* improve doc

* fix TestBroadcastEvidenceDuplicateVote

* Update rpc/core/evidence.go

Co-Authored-By: Thane Thomson <connect@thanethomson.com>

* add changelog entry

* fix TestBroadcastEvidenceDuplicateVote
  • Loading branch information
melekes committed Jul 22, 2019
1 parent 657832a commit c6daa48
Show file tree
Hide file tree
Showing 13 changed files with 302 additions and 29 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ program](https://hackerone.com/tendermint).
- Apps

- Go API
- [libs] \#3811 Remove `db` from libs in favor of `https://github.com/tendermint/tm-cmn`
- [libs] \#3811 Remove `db` from libs in favor of `https://github.com/tendermint/tm-cmn`

### FEATURES:

### IMPROVEMENTS:

- [abci] \#3809 Recover from application panics in `server/socket_server.go` to allow socket cleanup (@ruseinov)
- [rpc] \#2252 Add `/broadcast_evidence` endpoint to submit double signing and other types of evidence
- [rpc] \#3818 Make `max_body_bytes` and `max_header_bytes` configurable
- [p2p] \#3664 p2p/conn: reuse buffer when write/read from secret connection

Expand Down
1 change: 1 addition & 0 deletions abci/example/kvstore/kvstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ func (app *KVStoreApplication) Commit() types.ResponseCommit {
return types.ResponseCommit{Data: appHash}
}

// Returns an associated value or nil if missing.
func (app *KVStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
if reqQuery.Prove {
value := app.state.db.Get(prefixKey(reqQuery.Data))
Expand Down
45 changes: 41 additions & 4 deletions abci/example/kvstore/persistent_kvstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (

"github.com/tendermint/tendermint/abci/example/code"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types"
dbm "github.com/tendermint/tm-cmn/db"
)

Expand All @@ -27,6 +29,8 @@ type PersistentKVStoreApplication struct {
// validator set
ValUpdates []types.ValidatorUpdate

valAddrToPubKeyMap map[string]types.PubKey

logger log.Logger
}

Expand All @@ -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(),
}
}

Expand Down Expand Up @@ -83,8 +88,20 @@ func (app *PersistentKVStoreApplication) Commit() types.ResponseCommit {
return app.app.Commit()
}

func (app *PersistentKVStoreApplication) Query(reqQuery types.RequestQuery) types.ResponseQuery {
return app.app.Query(reqQuery)
// When path=/val and data={validator address}, returns the validator update (types.ValidatorUpdate) varint encoded.
// For any other path, returns an associated value or nil if missing.
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
Expand All @@ -102,6 +119,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{}
}

Expand Down Expand Up @@ -174,6 +205,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) {
Expand All @@ -183,6 +218,7 @@ func (app *PersistentKVStoreApplication) updateValidator(v types.ValidatorUpdate
Log: fmt.Sprintf("Cannot remove non-existent validator %s", pubStr)}
}
app.app.state.db.Delete(key)
delete(app.valAddrToPubKeyMap, string(pubkey.Address()))
} else {
// add or update validator
value := bytes.NewBuffer(make([]byte, 0))
Expand All @@ -192,6 +228,7 @@ 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
Expand Down
12 changes: 12 additions & 0 deletions rpc/client/amino.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package client

import (
amino "github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/types"
)

var cdc = amino.NewCodec()

func init() {
types.RegisterEvidences(cdc)
}
9 changes: 9 additions & 0 deletions rpc/client/httpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,15 @@ func (c *baseRPCClient) Validators(height *int64) (*ctypes.ResultValidators, err
return result, nil
}

func (c *baseRPCClient) BroadcastEvidence(ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
result := new(ctypes.ResultBroadcastEvidence)
_, err := c.caller.Call("broadcast_evidence", map[string]interface{}{"evidence": ev}, result)
if err != nil {
return nil, errors.Wrap(err, "BroadcastEvidence")
}
return result, nil
}

//-----------------------------------------------------------------------------
// WSEvents

Expand Down
52 changes: 30 additions & 22 deletions rpc/client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,24 @@ import (
"github.com/tendermint/tendermint/types"
)

// ABCIClient groups together the functionality that principally
// affects the ABCI app. In many cases this will be all we want,
// so we can accept an interface which is easier to mock
// Client wraps most important rpc calls a client would make if you want to
// listen for events, test if it also implements events.EventSwitch.
type Client interface {
cmn.Service
ABCIClient
EventsClient
HistoryClient
NetworkClient
SignClient
StatusClient
EvidenceClient
}

// ABCIClient groups together the functionality that principally affects the
// ABCI app.
//
// In many cases this will be all we want, so we can accept an interface which
// is easier to mock.
type ABCIClient interface {
// Reading from abci app
ABCIInfo() (*ctypes.ResultABCIInfo, error)
Expand All @@ -44,8 +59,8 @@ type ABCIClient interface {
BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error)
}

// SignClient groups together the interfaces need to get valid
// signatures and prove anything about the chain
// SignClient groups together the functionality needed to get valid signatures
// and prove anything about the chain.
type SignClient interface {
Block(height *int64) (*ctypes.ResultBlock, error)
BlockResults(height *int64) (*ctypes.ResultBlockResults, error)
Expand All @@ -55,32 +70,19 @@ type SignClient interface {
TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error)
}

// HistoryClient shows us data from genesis to now in large chunks.
// HistoryClient provides access to data from genesis to now in large chunks.
type HistoryClient interface {
Genesis() (*ctypes.ResultGenesis, error)
BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error)
}

// StatusClient provides access to general chain info.
type StatusClient interface {
// General chain info
Status() (*ctypes.ResultStatus, error)
}

// Client wraps most important rpc calls a client would make
// if you want to listen for events, test if it also
// implements events.EventSwitch
type Client interface {
cmn.Service
ABCIClient
EventsClient
HistoryClient
NetworkClient
SignClient
StatusClient
}

// NetworkClient is general info about the network state. May not
// be needed usually.
// NetworkClient is general info about the network state. May not be needed
// usually.
type NetworkClient interface {
NetInfo() (*ctypes.ResultNetInfo, error)
DumpConsensusState() (*ctypes.ResultDumpConsensusState, error)
Expand Down Expand Up @@ -110,3 +112,9 @@ type MempoolClient interface {
UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error)
NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error)
}

// EvidenceClient is used for submitting an evidence of the malicious
// behaviour.
type EvidenceClient interface {
BroadcastEvidence(ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error)
}
4 changes: 4 additions & 0 deletions rpc/client/localclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ func (c *Local) TxSearch(query string, prove bool, page, perPage int) (*ctypes.R
return core.TxSearch(c.ctx, query, prove, page, perPage)
}

func (c *Local) BroadcastEvidence(ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
return core.BroadcastEvidence(c.ctx, ev)
}

func (c *Local) Subscribe(ctx context.Context, subscriber, query string, outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) {
q, err := tmquery.New(query)
if err != nil {
Expand Down
7 changes: 6 additions & 1 deletion rpc/client/main_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package client_test

import (
"io/ioutil"
"os"
"testing"

Expand All @@ -13,7 +14,11 @@ 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 {
panic(err)
}
app := kvstore.NewPersistentKVStoreApplication(dir)
node = rpctest.StartTendermint(app)

code := m.Run()
Expand Down
5 changes: 5 additions & 0 deletions rpc/client/mock/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Client struct {
client.HistoryClient
client.StatusClient
client.EventsClient
client.EvidenceClient
cmn.Service
}

Expand Down Expand Up @@ -147,3 +148,7 @@ func (c Client) Commit(height *int64) (*ctypes.ResultCommit, error) {
func (c Client) Validators(height *int64) (*ctypes.ResultValidators, error) {
return core.Validators(&rpctypes.Context{}, height)
}

func (c Client) BroadcastEvidence(ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
return core.BroadcastEvidence(&rpctypes.Context{}, ev)
}
Loading

0 comments on commit c6daa48

Please sign in to comment.