|
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
cmn "github.com/tendermint/tendermint/libs/common"
|
|
|
|
"github.com/tendermint/tendermint/crypto/merkle"
|
|
"github.com/tendermint/tendermint/lite"
|
|
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
|
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
|
rpctypes "github.com/tendermint/tendermint/rpc/lib/types"
|
|
)
|
|
|
|
var _ rpcclient.Client = Wrapper{}
|
|
|
|
// Wrapper wraps a rpcclient with a Verifier and double-checks any input that is
|
|
// provable before passing it along. Allows you to make any rpcclient fully secure.
|
|
type Wrapper struct {
|
|
rpcclient.Client
|
|
cert *lite.DynamicVerifier
|
|
prt *merkle.ProofRuntime
|
|
}
|
|
|
|
// SecureClient uses a given Verifier to wrap an connection to an untrusted
|
|
// host and return a cryptographically secure rpc client.
|
|
//
|
|
// If it is wrapping an HTTP rpcclient, it will also wrap the websocket interface
|
|
func SecureClient(c rpcclient.Client, cert *lite.DynamicVerifier) Wrapper {
|
|
prt := defaultProofRuntime()
|
|
wrap := Wrapper{c, cert, prt}
|
|
// TODO: no longer possible as no more such interface exposed....
|
|
// if we wrap http client, then we can swap out the event switch to filter
|
|
// if hc, ok := c.(*rpcclient.HTTP); ok {
|
|
// evt := hc.WSEvents.EventSwitch
|
|
// hc.WSEvents.EventSwitch = WrappedSwitch{evt, wrap}
|
|
// }
|
|
return wrap
|
|
}
|
|
|
|
// ABCIQueryWithOptions exposes all options for the ABCI query and verifies the returned proof
|
|
func (w Wrapper) ABCIQueryWithOptions(path string, data cmn.HexBytes,
|
|
opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) {
|
|
|
|
res, err := GetWithProofOptions(w.prt, path, data, opts, w.Client, w.cert)
|
|
return res, err
|
|
}
|
|
|
|
// ABCIQuery uses default options for the ABCI query and verifies the returned proof
|
|
func (w Wrapper) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) {
|
|
return w.ABCIQueryWithOptions(path, data, rpcclient.DefaultABCIQueryOptions)
|
|
}
|
|
|
|
// Tx queries for a given tx and verifies the proof if it was requested
|
|
func (w Wrapper) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
|
|
res, err := w.Client.Tx(hash, prove)
|
|
if !prove || err != nil {
|
|
return res, err
|
|
}
|
|
h := res.Height
|
|
sh, err := GetCertifiedCommit(h, w.Client, w.cert)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
err = res.Proof.Validate(sh.DataHash)
|
|
return res, err
|
|
}
|
|
|
|
// BlockchainInfo requests a list of headers and verifies them all...
|
|
// Rather expensive.
|
|
//
|
|
// TODO: optimize this if used for anything needing performance
|
|
func (w Wrapper) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
|
|
r, err := w.Client.BlockchainInfo(minHeight, maxHeight)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// go and verify every blockmeta in the result....
|
|
for _, meta := range r.BlockMetas {
|
|
// get a checkpoint to verify from
|
|
res, err := w.Commit(&meta.Header.Height)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sh := res.SignedHeader
|
|
err = ValidateBlockMeta(meta, sh)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// Block returns an entire block and verifies all signatures
|
|
func (w Wrapper) Block(height *int64) (*ctypes.ResultBlock, error) {
|
|
resBlock, err := w.Client.Block(height)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// get a checkpoint to verify from
|
|
resCommit, err := w.Commit(height)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sh := resCommit.SignedHeader
|
|
|
|
err = ValidateBlock(resBlock.Block, sh)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return resBlock, nil
|
|
}
|
|
|
|
// Commit downloads the Commit and certifies it with the lite.
|
|
//
|
|
// This is the foundation for all other verification in this module
|
|
func (w Wrapper) Commit(height *int64) (*ctypes.ResultCommit, error) {
|
|
if height == nil {
|
|
resStatus, err := w.Client.Status()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// NOTE: If resStatus.CatchingUp, there is a race
|
|
// condition where the validator set for the next height
|
|
// isn't available until some time after the blockstore
|
|
// has height h on the remote node. This isn't an issue
|
|
// once the node has caught up, and a syncing node likely
|
|
// won't have this issue esp with the implementation we
|
|
// have here, but we may have to address this at some
|
|
// point.
|
|
height = new(int64)
|
|
*height = resStatus.SyncInfo.LatestBlockHeight
|
|
}
|
|
rpcclient.WaitForHeight(w.Client, *height, nil)
|
|
res, err := w.Client.Commit(height)
|
|
// if we got it, then verify it
|
|
if err == nil {
|
|
sh := res.SignedHeader
|
|
err = w.cert.Verify(sh)
|
|
}
|
|
return res, err
|
|
}
|
|
|
|
func (w Wrapper) RegisterOpDecoder(typ string, dec merkle.OpDecoder) {
|
|
w.prt.RegisterOpDecoder(typ, dec)
|
|
}
|
|
|
|
// SubscribeWS subscribes for events using the given query and remote address as
|
|
// a subscriber, but does not verify responses (UNSAFE)!
|
|
func (w Wrapper) SubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, error) {
|
|
out, err := w.Client.Subscribe(context.Background(), ctx.RemoteAddr(), query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
select {
|
|
case resultEvent := <-out:
|
|
// XXX(melekes) We should have a switch here that performs a validation
|
|
// depending on the event's type.
|
|
ctx.WSConn.TryWriteRPCResponse(
|
|
rpctypes.NewRPCSuccessResponse(
|
|
ctx.WSConn.Codec(),
|
|
rpctypes.JSONRPCStringID(fmt.Sprintf("%v#event", ctx.JSONReq.ID)),
|
|
resultEvent,
|
|
))
|
|
case <-w.Client.Quit():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return &ctypes.ResultSubscribe{}, nil
|
|
}
|
|
|
|
// UnsubscribeWS calls original client's Unsubscribe using remote address as a
|
|
// subscriber.
|
|
func (w Wrapper) UnsubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) {
|
|
err := w.Client.Unsubscribe(context.Background(), ctx.RemoteAddr(), query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ctypes.ResultUnsubscribe{}, nil
|
|
}
|
|
|
|
// UnsubscribeAllWS calls original client's UnsubscribeAll using remote address
|
|
// as a subscriber.
|
|
func (w Wrapper) UnsubscribeAllWS(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) {
|
|
err := w.Client.UnsubscribeAll(context.Background(), ctx.RemoteAddr())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ctypes.ResultUnsubscribe{}, nil
|
|
}
|
|
|
|
// // WrappedSwitch creates a websocket connection that auto-verifies any info
|
|
// // coming through before passing it along.
|
|
// //
|
|
// // Since the verification takes 1-2 rpc calls, this is obviously only for
|
|
// // relatively low-throughput situations that can tolerate a bit extra latency
|
|
// type WrappedSwitch struct {
|
|
// types.EventSwitch
|
|
// client rpcclient.Client
|
|
// }
|
|
|
|
// // FireEvent verifies any block or header returned from the eventswitch
|
|
// func (s WrappedSwitch) FireEvent(event string, data events.EventData) {
|
|
// tm, ok := data.(types.TMEventData)
|
|
// if !ok {
|
|
// fmt.Printf("bad type %#v\n", data)
|
|
// return
|
|
// }
|
|
|
|
// // check to validate it if possible, and drop if not valid
|
|
// switch t := tm.(type) {
|
|
// case types.EventDataNewBlockHeader:
|
|
// err := verifyHeader(s.client, t.Header)
|
|
// if err != nil {
|
|
// fmt.Printf("Invalid header: %#v\n", err)
|
|
// return
|
|
// }
|
|
// case types.EventDataNewBlock:
|
|
// err := verifyBlock(s.client, t.Block)
|
|
// if err != nil {
|
|
// fmt.Printf("Invalid block: %#v\n", err)
|
|
// return
|
|
// }
|
|
// // TODO: can we verify tx as well? anything else
|
|
// }
|
|
|
|
// // looks good, we fire it
|
|
// s.EventSwitch.FireEvent(event, data)
|
|
// }
|
|
|
|
// func verifyHeader(c rpcclient.Client, head *types.Header) error {
|
|
// // get a checkpoint to verify from
|
|
// commit, err := c.Commit(&head.Height)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// check := certclient.CommitFromResult(commit)
|
|
// return ValidateHeader(head, check)
|
|
// }
|
|
//
|
|
// func verifyBlock(c rpcclient.Client, block *types.Block) error {
|
|
// // get a checkpoint to verify from
|
|
// commit, err := c.Commit(&block.Height)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// check := certclient.CommitFromResult(commit)
|
|
// return ValidateBlock(block, check)
|
|
// }
|