@ -0,0 +1,60 @@ | |||
package commands | |||
import ( | |||
"github.com/spf13/cobra" | |||
cmn "github.com/tendermint/tmlibs/common" | |||
"github.com/tendermint/tendermint/lite/proxy" | |||
rpcclient "github.com/tendermint/tendermint/rpc/client" | |||
) | |||
// LiteCmd represents the base command when called without any subcommands | |||
var LiteCmd = &cobra.Command{ | |||
Use: "lite", | |||
Short: "Run lite-client proxy server, verifying tendermint rpc", | |||
Long: `This node will run a secure proxy to a tendermint rpc server. | |||
All calls that can be tracked back to a block header by a proof | |||
will be verified before passing them back to the caller. Other that | |||
that it will present the same interface as a full tendermint node, | |||
just with added trust and running locally.`, | |||
RunE: runProxy, | |||
SilenceUsage: true, | |||
} | |||
var ( | |||
listenAddr string | |||
nodeAddr string | |||
chainID string | |||
home string | |||
) | |||
func init() { | |||
LiteCmd.Flags().StringVar(&listenAddr, "laddr", ":8888", "Serve the proxy on the given port") | |||
LiteCmd.Flags().StringVar(&nodeAddr, "node", "localhost:46657", "Connect to a Tendermint node at this address") | |||
LiteCmd.Flags().StringVar(&chainID, "chain-id", "tendermint", "Specify the Tendermint chain ID") | |||
LiteCmd.Flags().StringVar(&home, "home-dir", ".tendermint-lite", "Specify the home directory") | |||
} | |||
func runProxy(cmd *cobra.Command, args []string) error { | |||
// First, connect a client | |||
node := rpcclient.NewHTTP(nodeAddr, "/websocket") | |||
cert, err := proxy.GetCertifier(chainID, home, nodeAddr) | |||
if err != nil { | |||
return err | |||
} | |||
sc := proxy.SecureClient(node, cert) | |||
err = proxy.StartProxy(sc, listenAddr, logger) | |||
if err != nil { | |||
return err | |||
} | |||
cmn.TrapSignal(func() { | |||
// TODO: close up shop | |||
}) | |||
return nil | |||
} |
@ -0,0 +1,40 @@ | |||
package proxy | |||
import ( | |||
"bytes" | |||
"github.com/pkg/errors" | |||
"github.com/tendermint/tendermint/lite" | |||
certerr "github.com/tendermint/tendermint/lite/errors" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
func ValidateBlockMeta(meta *types.BlockMeta, check lite.Commit) error { | |||
// TODO: check the BlockID?? | |||
return ValidateHeader(meta.Header, check) | |||
} | |||
func ValidateBlock(meta *types.Block, check lite.Commit) error { | |||
err := ValidateHeader(meta.Header, check) | |||
if err != nil { | |||
return err | |||
} | |||
if !bytes.Equal(meta.Data.Hash(), meta.Header.DataHash) { | |||
return errors.New("Data hash doesn't match header") | |||
} | |||
return nil | |||
} | |||
func ValidateHeader(head *types.Header, check lite.Commit) error { | |||
// make sure they are for the same height (obvious fail) | |||
if head.Height != check.Height() { | |||
return certerr.ErrHeightMismatch(head.Height, check.Height()) | |||
} | |||
// check if they are equal by using hashes | |||
chead := check.Header | |||
if !bytes.Equal(head.Hash(), chead.Hash()) { | |||
return errors.New("Headers don't match") | |||
} | |||
return nil | |||
} |
@ -0,0 +1,30 @@ | |||
package proxy | |||
import ( | |||
"github.com/tendermint/tendermint/lite" | |||
certclient "github.com/tendermint/tendermint/lite/client" | |||
"github.com/tendermint/tendermint/lite/files" | |||
) | |||
func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.Inquiring, error) { | |||
trust := lite.NewCacheProvider( | |||
lite.NewMemStoreProvider(), | |||
files.NewProvider(rootDir), | |||
) | |||
source := certclient.NewHTTPProvider(nodeAddr) | |||
// XXX: total insecure hack to avoid `init` | |||
fc, err := source.LatestCommit() | |||
/* XXX | |||
// this gets the most recent verified commit | |||
fc, err := trust.LatestCommit() | |||
if certerr.IsCommitNotFoundErr(err) { | |||
return nil, errors.New("Please run init first to establish a root of trust") | |||
}*/ | |||
if err != nil { | |||
return nil, err | |||
} | |||
cert := lite.NewInquiring(chainID, fc, trust, source) | |||
return cert, nil | |||
} |
@ -0,0 +1,22 @@ | |||
package proxy | |||
import ( | |||
"fmt" | |||
"github.com/pkg/errors" | |||
) | |||
//-------------------------------------------- | |||
var errNoData = fmt.Errorf("No data returned for query") | |||
// IsNoDataErr checks whether an error is due to a query returning empty data | |||
func IsNoDataErr(err error) bool { | |||
return errors.Cause(err) == errNoData | |||
} | |||
func ErrNoData() error { | |||
return errors.WithStack(errNoData) | |||
} | |||
//-------------------------------------------- |
@ -0,0 +1,17 @@ | |||
package proxy | |||
import ( | |||
"errors" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
) | |||
func TestErrorNoData(t *testing.T) { | |||
e1 := ErrNoData() | |||
assert.True(t, IsNoDataErr(e1)) | |||
e2 := errors.New("foobar") | |||
assert.False(t, IsNoDataErr(e2)) | |||
assert.False(t, IsNoDataErr(nil)) | |||
} |
@ -0,0 +1,69 @@ | |||
package proxy | |||
import ( | |||
"net/http" | |||
"github.com/tendermint/tmlibs/log" | |||
rpcclient "github.com/tendermint/tendermint/rpc/client" | |||
"github.com/tendermint/tendermint/rpc/core" | |||
rpc "github.com/tendermint/tendermint/rpc/lib/server" | |||
) | |||
const ( | |||
wsEndpoint = "/websocket" | |||
) | |||
// StartProxy will start the websocket manager on the client, | |||
// set up the rpc routes to proxy via the given client, | |||
// and start up an http/rpc server on the location given by bind (eg. :1234) | |||
func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger) error { | |||
c.Start() | |||
r := RPCRoutes(c) | |||
// build the handler... | |||
mux := http.NewServeMux() | |||
rpc.RegisterRPCFuncs(mux, r, logger) | |||
wm := rpc.NewWebsocketManager(r, rpc.EventSubscriber(c)) | |||
wm.SetLogger(logger) | |||
core.SetLogger(logger) | |||
mux.HandleFunc(wsEndpoint, wm.WebsocketHandler) | |||
_, err := rpc.StartHTTPServer(listenAddr, mux, logger) | |||
return err | |||
} | |||
// RPCRoutes just routes everything to the given client, as if it were | |||
// a tendermint fullnode. | |||
// | |||
// if we want security, the client must implement it as a secure client | |||
func RPCRoutes(c rpcclient.Client) map[string]*rpc.RPCFunc { | |||
return map[string]*rpc.RPCFunc{ | |||
// Subscribe/unsubscribe are reserved for websocket events. | |||
// We can just use the core tendermint impl, which uses the | |||
// EventSwitch we registered in NewWebsocketManager above | |||
"subscribe": rpc.NewWSRPCFunc(core.Subscribe, "query"), | |||
"unsubscribe": rpc.NewWSRPCFunc(core.Unsubscribe, "query"), | |||
// info API | |||
"status": rpc.NewRPCFunc(c.Status, ""), | |||
"blockchain": rpc.NewRPCFunc(c.BlockchainInfo, "minHeight,maxHeight"), | |||
"genesis": rpc.NewRPCFunc(c.Genesis, ""), | |||
"block": rpc.NewRPCFunc(c.Block, "height"), | |||
"commit": rpc.NewRPCFunc(c.Commit, "height"), | |||
"tx": rpc.NewRPCFunc(c.Tx, "hash,prove"), | |||
"validators": rpc.NewRPCFunc(c.Validators, ""), | |||
// broadcast API | |||
"broadcast_tx_commit": rpc.NewRPCFunc(c.BroadcastTxCommit, "tx"), | |||
"broadcast_tx_sync": rpc.NewRPCFunc(c.BroadcastTxSync, "tx"), | |||
"broadcast_tx_async": rpc.NewRPCFunc(c.BroadcastTxAsync, "tx"), | |||
// abci API | |||
"abci_query": rpc.NewRPCFunc(c.ABCIQuery, "path,data,prove"), | |||
"abci_info": rpc.NewRPCFunc(c.ABCIInfo, ""), | |||
} | |||
} |
@ -0,0 +1,120 @@ | |||
package proxy | |||
import ( | |||
"github.com/pkg/errors" | |||
"github.com/tendermint/go-wire/data" | |||
"github.com/tendermint/iavl" | |||
"github.com/tendermint/tendermint/lite" | |||
"github.com/tendermint/tendermint/lite/client" | |||
certerr "github.com/tendermint/tendermint/lite/errors" | |||
rpcclient "github.com/tendermint/tendermint/rpc/client" | |||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | |||
) | |||
// GetWithProof will query the key on the given node, and verify it has | |||
// a valid proof, as defined by the certifier. | |||
// | |||
// If there is any error in checking, returns an error. | |||
// If val is non-empty, proof should be KeyExistsProof | |||
// If val is empty, proof should be KeyMissingProof | |||
func GetWithProof(key []byte, reqHeight int64, node rpcclient.Client, | |||
cert lite.Certifier) ( | |||
val data.Bytes, height int64, proof iavl.KeyProof, err error) { | |||
if reqHeight < 0 { | |||
err = errors.Errorf("Height cannot be negative") | |||
return | |||
} | |||
_resp, proof, err := GetWithProofOptions("/key", key, | |||
rpcclient.ABCIQueryOptions{Height: int64(reqHeight)}, | |||
node, cert) | |||
if _resp != nil { | |||
resp := _resp.Response | |||
val, height = resp.Value, resp.Height | |||
} | |||
return val, height, proof, err | |||
} | |||
// GetWithProofOptions is useful if you want full access to the ABCIQueryOptions | |||
func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOptions, | |||
node rpcclient.Client, cert lite.Certifier) ( | |||
*ctypes.ResultABCIQuery, iavl.KeyProof, error) { | |||
_resp, err := node.ABCIQueryWithOptions(path, key, opts) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
resp := _resp.Response | |||
// make sure the proof is the proper height | |||
if resp.IsErr() { | |||
err = errors.Errorf("Query error %d: %d", resp.Code) | |||
return nil, nil, err | |||
} | |||
if len(resp.Key) == 0 || len(resp.Proof) == 0 { | |||
return nil, nil, ErrNoData() | |||
} | |||
if resp.Height == 0 { | |||
return nil, nil, errors.New("Height returned is zero") | |||
} | |||
// AppHash for height H is in header H+1 | |||
commit, err := GetCertifiedCommit(resp.Height+1, node, cert) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
if len(resp.Value) > 0 { | |||
// The key was found, construct a proof of existence. | |||
eproof, err := iavl.ReadKeyExistsProof(resp.Proof) | |||
if err != nil { | |||
return nil, nil, errors.Wrap(err, "Error reading proof") | |||
} | |||
// Validate the proof against the certified header to ensure data integrity. | |||
err = eproof.Verify(resp.Key, resp.Value, commit.Header.AppHash) | |||
if err != nil { | |||
return nil, nil, errors.Wrap(err, "Couldn't verify proof") | |||
} | |||
return &ctypes.ResultABCIQuery{resp}, eproof, nil | |||
} | |||
// The key wasn't found, construct a proof of non-existence. | |||
var aproof *iavl.KeyAbsentProof | |||
aproof, err = iavl.ReadKeyAbsentProof(resp.Proof) | |||
if err != nil { | |||
return nil, nil, errors.Wrap(err, "Error reading proof") | |||
} | |||
// Validate the proof against the certified header to ensure data integrity. | |||
err = aproof.Verify(resp.Key, nil, commit.Header.AppHash) | |||
if err != nil { | |||
return nil, nil, errors.Wrap(err, "Couldn't verify proof") | |||
} | |||
return &ctypes.ResultABCIQuery{resp}, aproof, ErrNoData() | |||
} | |||
// GetCertifiedCommit gets the signed header for a given height | |||
// and certifies it. Returns error if unable to get a proven header. | |||
func GetCertifiedCommit(h int64, node rpcclient.Client, | |||
cert lite.Certifier) (empty lite.Commit, err error) { | |||
// FIXME: cannot use cert.GetByHeight for now, as it also requires | |||
// Validators and will fail on querying tendermint for non-current height. | |||
// When this is supported, we should use it instead... | |||
rpcclient.WaitForHeight(node, h, nil) | |||
cresp, err := node.Commit(&h) | |||
if err != nil { | |||
return | |||
} | |||
commit := client.CommitFromResult(cresp) | |||
// validate downloaded checkpoint with our request and trust store. | |||
if commit.Height() != h { | |||
return empty, certerr.ErrHeightMismatch(h, commit.Height()) | |||
} | |||
err = cert.Certify(commit) | |||
return commit, nil | |||
} |
@ -0,0 +1,140 @@ | |||
package proxy | |||
import ( | |||
"fmt" | |||
"os" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
"github.com/tendermint/abci/example/dummy" | |||
"github.com/tendermint/tendermint/lite" | |||
certclient "github.com/tendermint/tendermint/lite/client" | |||
nm "github.com/tendermint/tendermint/node" | |||
"github.com/tendermint/tendermint/rpc/client" | |||
rpctest "github.com/tendermint/tendermint/rpc/test" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
var node *nm.Node | |||
// TODO fix tests!! | |||
func TestMain(m *testing.M) { | |||
app := dummy.NewDummyApplication() | |||
node = rpctest.StartTendermint(app) | |||
code := m.Run() | |||
node.Stop() | |||
node.Wait() | |||
os.Exit(code) | |||
} | |||
func dummyTx(k, v []byte) []byte { | |||
return []byte(fmt.Sprintf("%s=%s", k, v)) | |||
} | |||
func _TestAppProofs(t *testing.T) { | |||
assert, require := assert.New(t), require.New(t) | |||
cl := client.NewLocal(node) | |||
client.WaitForHeight(cl, 1, nil) | |||
k := []byte("my-key") | |||
v := []byte("my-value") | |||
tx := dummyTx(k, v) | |||
br, err := cl.BroadcastTxCommit(tx) | |||
require.NoError(err, "%+v", err) | |||
require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx) | |||
require.EqualValues(0, br.DeliverTx.Code) | |||
brh := br.Height | |||
// This sets up our trust on the node based on some past point. | |||
source := certclient.NewProvider(cl) | |||
seed, err := source.GetByHeight(brh - 2) | |||
require.NoError(err, "%+v", err) | |||
cert := lite.NewStatic("my-chain", seed.Validators) | |||
client.WaitForHeight(cl, 3, nil) | |||
latest, err := source.LatestCommit() | |||
require.NoError(err, "%+v", err) | |||
rootHash := latest.Header.AppHash | |||
// verify a query before the tx block has no data (and valid non-exist proof) | |||
bs, height, proof, err := GetWithProof(k, brh-1, cl, cert) | |||
fmt.Println(bs, height, proof, err) | |||
require.NotNil(err) | |||
require.True(IsNoDataErr(err), err.Error()) | |||
require.Nil(bs) | |||
// but given that block it is good | |||
bs, height, proof, err = GetWithProof(k, brh, cl, cert) | |||
require.NoError(err, "%+v", err) | |||
require.NotNil(proof) | |||
require.True(height >= int64(latest.Header.Height)) | |||
// Alexis there is a bug here, somehow the above code gives us rootHash = nil | |||
// and proof.Verify doesn't care, while proofNotExists.Verify fails. | |||
// I am hacking this in to make it pass, but please investigate further. | |||
rootHash = proof.Root() | |||
//err = wire.ReadBinaryBytes(bs, &data) | |||
//require.NoError(err, "%+v", err) | |||
assert.EqualValues(v, bs) | |||
err = proof.Verify(k, bs, rootHash) | |||
assert.NoError(err, "%+v", err) | |||
// Test non-existing key. | |||
missing := []byte("my-missing-key") | |||
bs, _, proof, err = GetWithProof(missing, 0, cl, cert) | |||
require.True(IsNoDataErr(err)) | |||
require.Nil(bs) | |||
require.NotNil(proof) | |||
err = proof.Verify(missing, nil, rootHash) | |||
assert.NoError(err, "%+v", err) | |||
err = proof.Verify(k, nil, rootHash) | |||
assert.Error(err) | |||
} | |||
func _TestTxProofs(t *testing.T) { | |||
assert, require := assert.New(t), require.New(t) | |||
cl := client.NewLocal(node) | |||
client.WaitForHeight(cl, 1, nil) | |||
tx := dummyTx([]byte("key-a"), []byte("value-a")) | |||
br, err := cl.BroadcastTxCommit(tx) | |||
require.NoError(err, "%+v", err) | |||
require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx) | |||
require.EqualValues(0, br.DeliverTx.Code) | |||
brh := br.Height | |||
source := certclient.NewProvider(cl) | |||
seed, err := source.GetByHeight(brh - 2) | |||
require.NoError(err, "%+v", err) | |||
cert := lite.NewStatic("my-chain", seed.Validators) | |||
// First let's make sure a bogus transaction hash returns a valid non-existence proof. | |||
key := types.Tx([]byte("bogus")).Hash() | |||
res, err := cl.Tx(key, true) | |||
require.NotNil(err) | |||
require.Contains(err.Error(), "not found") | |||
// Now let's check with the real tx hash. | |||
key = types.Tx(tx).Hash() | |||
res, err = cl.Tx(key, true) | |||
require.NoError(err, "%+v", err) | |||
require.NotNil(res) | |||
err = res.Proof.Validate(key) | |||
assert.NoError(err, "%+v", err) | |||
commit, err := GetCertifiedCommit(br.Height, cl, cert) | |||
require.Nil(err, "%+v", err) | |||
require.Equal(res.Proof.RootHash, commit.Header.DataHash) | |||
} |
@ -0,0 +1,185 @@ | |||
package proxy | |||
import ( | |||
"github.com/tendermint/go-wire/data" | |||
"github.com/tendermint/tendermint/lite" | |||
certclient "github.com/tendermint/tendermint/lite/client" | |||
rpcclient "github.com/tendermint/tendermint/rpc/client" | |||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | |||
) | |||
var _ rpcclient.Client = Wrapper{} | |||
// Wrapper wraps a rpcclient with a Certifier 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.Inquiring | |||
} | |||
// SecureClient uses a given certifier 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.Inquiring) Wrapper { | |||
wrap := Wrapper{c, cert} | |||
// 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 data.Bytes, opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { | |||
res, _, err := GetWithProofOptions(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 data.Bytes) (*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 := int64(res.Height) | |||
check, err := GetCertifiedCommit(h, w.Client, w.cert) | |||
if err != nil { | |||
return res, err | |||
} | |||
err = res.Proof.Validate(check.Header.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 | |||
c, err := w.Commit(&meta.Header.Height) | |||
if err != nil { | |||
return nil, err | |||
} | |||
check := certclient.CommitFromResult(c) | |||
err = ValidateBlockMeta(meta, check) | |||
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) { | |||
r, err := w.Client.Block(height) | |||
if err != nil { | |||
return nil, err | |||
} | |||
// get a checkpoint to verify from | |||
c, err := w.Commit(height) | |||
if err != nil { | |||
return nil, err | |||
} | |||
check := certclient.CommitFromResult(c) | |||
// now verify | |||
err = ValidateBlockMeta(r.BlockMeta, check) | |||
if err != nil { | |||
return nil, err | |||
} | |||
err = ValidateBlock(r.Block, check) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return r, 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) { | |||
rpcclient.WaitForHeight(w.Client, *height, nil) | |||
r, err := w.Client.Commit(height) | |||
// if we got it, then certify it | |||
if err == nil { | |||
check := certclient.CommitFromResult(r) | |||
err = w.cert.Certify(check) | |||
} | |||
return r, err | |||
} | |||
// // 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.Unwrap().(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) | |||
// } |