diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d5d377da..55b2f951a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,10 @@ BREAKING CHANGES: - consensus/wal: removed separator - rpc/client: changed Subscribe/Unsubscribe/UnsubscribeAll funcs signatures to be identical to event bus. +FEATURES: +- new `tendermint lite` command (and `lite/proxy` pkg) for running a light-client RPC proxy. + NOTE it is currently insecure and its APIs are not yet covered by semver + IMPROVEMENTS: - rpc/client: can act as event bus subscriber (See https://github.com/tendermint/tendermint/issues/945). - p2p: use exponential backoff from seconds to hours when attempting to reconnect to persistent peer diff --git a/cmd/tendermint/commands/lite.go b/cmd/tendermint/commands/lite.go new file mode 100644 index 000000000..d94f95ba4 --- /dev/null +++ b/cmd/tendermint/commands/lite.go @@ -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 +} diff --git a/cmd/tendermint/main.go b/cmd/tendermint/main.go index a46f227c5..c24cfe197 100644 --- a/cmd/tendermint/main.go +++ b/cmd/tendermint/main.go @@ -15,6 +15,7 @@ func main() { cmd.GenValidatorCmd, cmd.InitFilesCmd, cmd.ProbeUpnpCmd, + cmd.LiteCmd, cmd.ReplayCmd, cmd.ReplayConsoleCmd, cmd.ResetAllCmd, diff --git a/lite/proxy/block.go b/lite/proxy/block.go new file mode 100644 index 000000000..60cd00f0d --- /dev/null +++ b/lite/proxy/block.go @@ -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 +} diff --git a/lite/proxy/certifier.go b/lite/proxy/certifier.go new file mode 100644 index 000000000..1d7284f2c --- /dev/null +++ b/lite/proxy/certifier.go @@ -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 +} diff --git a/lite/proxy/errors.go b/lite/proxy/errors.go new file mode 100644 index 000000000..5a2713e3c --- /dev/null +++ b/lite/proxy/errors.go @@ -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) +} + +//-------------------------------------------- diff --git a/lite/proxy/errors_test.go b/lite/proxy/errors_test.go new file mode 100644 index 000000000..7f51be50f --- /dev/null +++ b/lite/proxy/errors_test.go @@ -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)) +} diff --git a/lite/proxy/proxy.go b/lite/proxy/proxy.go new file mode 100644 index 000000000..21db13ed4 --- /dev/null +++ b/lite/proxy/proxy.go @@ -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, ""), + } +} diff --git a/lite/proxy/query.go b/lite/proxy/query.go new file mode 100644 index 000000000..0a9d86a0e --- /dev/null +++ b/lite/proxy/query.go @@ -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 +} diff --git a/lite/proxy/query_test.go b/lite/proxy/query_test.go new file mode 100644 index 000000000..234f65e55 --- /dev/null +++ b/lite/proxy/query_test.go @@ -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) + +} diff --git a/lite/proxy/wrapper.go b/lite/proxy/wrapper.go new file mode 100644 index 000000000..a76c29426 --- /dev/null +++ b/lite/proxy/wrapper.go @@ -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) +// }