Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

service/state: Implement balance verification against AppHash #911

Merged
merged 13 commits into from
Jul 27, 2022
Merged
51 changes: 48 additions & 3 deletions docs/adr/adr-004-state-interaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,54 @@ type StateAccessor interface {

### Verification of Balances
In order to check that the balances returned via the `AccountBalance` query are correct, it is necessary to also request
Merkle proofs from the celestia-app and verify them against the latest head's `AppHash`. In order for the `StateAccessor`
to do this, it would need access to the `header.Store`'s `Head()` method in order to get the latest known header of the
node and check its `AppHash`.
Merkle proofs from celestia-app and verify them against the latest head's `AppHash`.

In order for the `StateAccessor` to do this, it would need access to the `header.Getter`'s `Head()` method in order to get the latest known header of the node and check its `AppHash`.
Then, instead of performing a regular `gRPC` query against the celestia-app's bank module, it would perform an ABCI request query via RPC as such:

```go
prefixedAccountKey := append(bank_types.CreateAccountBalancesPrefix(addr.Bytes()), []byte(app.BondDenom)...)
abciReq := abci.RequestQuery{
Path: fmt.Sprintf("store/%s/key", bank_types.StoreKey),
// we request the balance at the block *previous* to the current head as
// the AppHash committed to the head is calculated by applying the previous
// block's transaction list, not the current block, meaning the head's AppHash
// is actually a result of the previous block's transactions.
Height: head.Height - 1,
Data: prefixedAccountKey,
// we set this value to `true` in order to get proofs in the response
// that we can use to verify against the head's AppHash.
Prove: true,
}
result, err := ca.rpcCli.ABCIQueryWithOptions(ctx, abciReq.Path, abciReq.Data, rpc_client.ABCIQueryOptions{})
if err != nil {
return nil, err
}
```

The result of the above request will contain the balance of the queried address and proofs that can be used to verify
the returned balance against the current head's `AppHash`. The proofs are returned as the type `crypto.ProofOps` which
itself is not really functional until converted into a more useful wrapper, `MerkleProof`, provided by the `ibc-go`
[23-commitment/types pkg](https://github.com/cosmos/ibc-go/blob/main/modules/core/23-commitment/types/utils.go#L10).

Using `types.ConvertProofs()` returns a `types.MerkleProof` that wraps a chain of commitment proofs with which you can
verify the membership of the returned balance in the tree of the given root (the `AppHash` from the head), as such:

```go
// convert proofs into a more digestible format
merkleproof, err := proof_utils.ConvertProofs(result.Response.GetProofOps())
if err != nil {
return nil, err
}
root := proof_utils.NewMerkleRoot(head.AppHash)
// VerifyMembership expects the path as:
// []string{<store key of module>, <actual key corresponding to requested value>}
path := proof_utils.NewMerklePath(bank_types.StoreKey, string(prefixedAccountKey))
err = merkleproof.VerifyMembership(proof_utils.GetSDKSpecs(), root, path, result.Response.Value)
if err != nil {
return nil, err
}
```
Wondertan marked this conversation as resolved.
Show resolved Hide resolved

### Availability of `StateService` during sync
The `Syncer` in the `header` package provides one public method, `Finished()`, that indicates whether the syncer has
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ require (
github.com/celestiaorg/nmt v0.10.0
github.com/celestiaorg/rsmt2d v0.5.0
github.com/cosmos/cosmos-sdk v0.46.0-beta2.0.20220418184507-c53157dd63f6
github.com/cosmos/cosmos-sdk/api v0.1.0
github.com/cosmos/ibc-go/v4 v4.0.0-rc0
Wondertan marked this conversation as resolved.
Show resolved Hide resolved
github.com/dgraph-io/badger/v2 v2.2007.4
github.com/gammazero/workerpool v1.1.2
github.com/gogo/protobuf v1.3.3
Expand Down Expand Up @@ -61,7 +63,7 @@ require (
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
github.com/99designs/keyring v1.1.6 // indirect
github.com/Workiva/go-datastructures v1.0.53 // indirect
github.com/armon/go-metrics v0.3.10 // indirect
github.com/armon/go-metrics v0.4.0 // indirect
github.com/aws/aws-sdk-go v1.40.45 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
Expand Down Expand Up @@ -107,7 +109,6 @@ require (
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/gammazero/deque v0.1.0 // indirect
github.com/go-kit/kit v0.12.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
Expand Down
8 changes: 5 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q=
github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
Expand Down Expand Up @@ -234,6 +234,7 @@ github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/Tv
github.com/cosmos/cosmos-proto v1.0.0-alpha7 h1:yqYUOHF2jopwZh4dVQp3xgqwftE5/2hkrwIV6vkUbO0=
github.com/cosmos/cosmos-proto v1.0.0-alpha7/go.mod h1:dosO4pSAbJF8zWCzCoTWP7nNsjcvSUBQmniFxDg5daw=
github.com/cosmos/cosmos-sdk/api v0.1.0 h1:xfSKM0e9p+EJTMQnf5PbWE6VT8ruxTABIJ64Rd064dE=
github.com/cosmos/cosmos-sdk/api v0.1.0/go.mod h1:CupqQBskAOiTXO1XDZ/wrtWzN/wTxUvbQmOqdUhR8wI=
github.com/cosmos/cosmos-sdk/db v1.0.0-beta.1 h1:6YvzjQtc+cDwCe9XwYPPa8zFCxNG79N7vmCjpK+vGOg=
github.com/cosmos/cosmos-sdk/errors v1.0.0-beta.3 h1:Ep7FHNViVwwGnwLFEPewZYsyN2CJNVMmMvFmtNQtbnw=
github.com/cosmos/cosmos-sdk/errors v1.0.0-beta.3/go.mod h1:HFea93YKmoMJ/mNKtkSeJZDtyJ4inxBsUK928KONcqo=
Expand All @@ -243,6 +244,8 @@ github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4
github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw=
github.com/cosmos/iavl v0.18.0 h1:02ur4vnalMR2GuWCFNkuseUcl/BCVmg9tOeHOGiZOkE=
github.com/cosmos/iavl v0.18.0/go.mod h1:L0VZHfq0tqMNJvXlslGExaaiZM7eSm+90Vh9QUbp6j4=
github.com/cosmos/ibc-go/v4 v4.0.0-rc0 h1:zeMr6PNE7L300AcGkrMwRvtp62/RpGc7qU1LwhUcPKc=
github.com/cosmos/ibc-go/v4 v4.0.0-rc0/go.mod h1:4LK+uPycPhebJrJ8ebIqvsMEZ0lVRVNTiEyeI9zfB0U=
github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4=
github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY=
github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI=
Expand Down Expand Up @@ -373,7 +376,6 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
Expand Down
2 changes: 1 addition & 1 deletion node/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func baseComponents(cfg *Config, store Store) fx.Option {
fx.Invoke(invokeWatchdog(store.Path())),
p2p.Components(cfg.P2P),
// state components
statecomponents.Components(cfg.Core.IP, cfg.Core.GRPCPort, cfg.Key),
statecomponents.Components(cfg.Core, cfg.Key),
// RPC components
fx.Provide(rpc.Server(cfg.RPC)),
)
Expand Down
10 changes: 6 additions & 4 deletions node/state/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import (
"go.uber.org/fx"

apptypes "github.com/celestiaorg/celestia-app/x/payment/types"
"github.com/celestiaorg/celestia-node/header"
"github.com/celestiaorg/celestia-node/service/state"
)

// CoreAccessor constructs a new instance of state.Accessor over
// a celestia-core connection.
func CoreAccessor(
coreIP,
grpcPort string,
) func(fx.Lifecycle, *apptypes.KeyringSigner) (state.Accessor, error) {
return func(lc fx.Lifecycle, signer *apptypes.KeyringSigner) (state.Accessor, error) {
ca := state.NewCoreAccessor(signer, coreIP, grpcPort)
coreRPC,
coreGRPC string,
) func(fx.Lifecycle, *apptypes.KeyringSigner, header.Store) (state.Accessor, error) {
return func(lc fx.Lifecycle, signer *apptypes.KeyringSigner, getter header.Store) (state.Accessor, error) {
ca := state.NewCoreAccessor(signer, getter, coreIP, coreRPC, coreGRPC)
lc.Append(fx.Hook{
OnStart: ca.Start,
OnStop: ca.Stop,
Expand Down
18 changes: 13 additions & 5 deletions node/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"go.uber.org/fx"

"github.com/celestiaorg/celestia-node/fraud"
"github.com/celestiaorg/celestia-node/header"
"github.com/celestiaorg/celestia-node/libs/fxutil"
"github.com/celestiaorg/celestia-node/node/core"
"github.com/celestiaorg/celestia-node/node/key"
"github.com/celestiaorg/celestia-node/node/services"
"github.com/celestiaorg/celestia-node/service/state"
Expand All @@ -17,17 +19,23 @@ var log = logging.Logger("state-access-constructor")

// Components provides all components necessary to construct the
// state service.
func Components(coreIP, grpcPort string, cfg key.Config) fx.Option {
func Components(coreCfg core.Config, keyCfg key.Config) fx.Option {
return fx.Options(
fx.Provide(Keyring(cfg)),
fx.Provide(CoreAccessor(coreIP, grpcPort)),
fx.Provide(Keyring(keyCfg)),
fx.Provide(CoreAccessor(coreCfg.IP, coreCfg.RPCPort, coreCfg.GRPCPort)),
fx.Provide(Service),
)
}

// Service constructs a new state.Service.
func Service(ctx context.Context, lc fx.Lifecycle, accessor state.Accessor, fservice fraud.Service) *state.Service {
serv := state.NewService(accessor)
func Service(
ctx context.Context,
lc fx.Lifecycle,
accessor state.Accessor,
store header.Store,
fservice fraud.Service,
) *state.Service {
serv := state.NewService(accessor, store)
lifecycleCtx := fxutil.WithLifecycle(ctx, lc)
lc.Append(fx.Hook{
OnStart: func(startCtx context.Context) error {
Expand Down
2 changes: 1 addition & 1 deletion service/rpc/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
func (h *Handler) RegisterEndpoints(rpc *Server) {
// state endpoints
rpc.RegisterHandlerFunc(balanceEndpoint, h.handleBalanceRequest, http.MethodGet)
rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", balanceEndpoint, addrKey), h.handleBalanceForAddrRequest,
rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", balanceEndpoint, addrKey), h.handleBalanceRequest,
http.MethodGet)
rpc.RegisterHandlerFunc(submitTxEndpoint, h.handleSubmitTx, http.MethodPost)
rpc.RegisterHandlerFunc(submitPFDEndpoint, h.handleSubmitPFD, http.MethodPost)
Expand Down
41 changes: 17 additions & 24 deletions service/rpc/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (

"github.com/cosmos/cosmos-sdk/types"
"github.com/gorilla/mux"

"github.com/celestiaorg/celestia-node/service/state"
)

const (
Expand Down Expand Up @@ -39,33 +41,24 @@ type transferRequest struct {
}

func (h *Handler) handleBalanceRequest(w http.ResponseWriter, r *http.Request) {
bal, err := h.state.Balance(r.Context())
if err != nil {
writeError(w, http.StatusInternalServerError, balanceEndpoint, err)
return
}
resp, err := json.Marshal(bal)
if err != nil {
writeError(w, http.StatusInternalServerError, balanceEndpoint, err)
return
}
_, err = w.Write(resp)
if err != nil {
log.Errorw("writing response", "endpoint", balanceEndpoint, "err", err)
}
}

func (h *Handler) handleBalanceForAddrRequest(w http.ResponseWriter, r *http.Request) {
var (
bal *state.Balance
err error
)
// read and parse request
vars := mux.Vars(r)
addrStr := vars[addrKey]
// convert address to Address type
addr, err := types.AccAddressFromBech32(addrStr)
if err != nil {
writeError(w, http.StatusBadRequest, balanceEndpoint, err)
return
addrStr, exists := vars[addrKey]
if exists {
// convert address to Address type
addr, addrerr := types.AccAddressFromBech32(addrStr)
if addrerr != nil {
writeError(w, http.StatusBadRequest, balanceEndpoint, addrerr)
return
}
bal, err = h.state.BalanceForAddress(r.Context(), addr)
} else {
bal, err = h.state.Balance(r.Context())
}
bal, err := h.state.BalanceForAddress(r.Context(), addr)
if err != nil {
writeError(w, http.StatusInternalServerError, balanceEndpoint, err)
return
Expand Down
Loading