Skip to content

Commit

Permalink
service/state: Implement balance verification against AppHash (cele…
Browse files Browse the repository at this point in the history
…stiaorg#911)

* refactor: extract HeaderGetter interface from daser into header pkg

* refactor: delete interface.go file from das pkg

* refactor: Store interface wraps getter methods

* chore: rebase fixes

* feat: add `/verified_balance` endpoint

* chore: lint

* refactor: dedup handleBalanceRequest

* docs: update state ADR

* refactor: consolidate verified_balance and balance endpoints --> verify balance by default

* chore: replace TODOs with PR refs

* refactor: apply john review comments

* refactor: extract sdk error func into helpers file, rename import aliases

* doc: update ADR header.Store -> header.Getter
  • Loading branch information
renaynay authored and rootulp committed Jul 27, 2022
1 parent c835168 commit 2d56c7d
Show file tree
Hide file tree
Showing 12 changed files with 209 additions and 71 deletions.
59 changes: 52 additions & 7 deletions docs/adr/adr-004-state-interaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,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
}
```

### Availability of `StateService` during sync

Expand All @@ -95,24 +140,24 @@ start.
type CoreAccess struct {
signer *apptypes.KeyringSigner
encCfg cosmoscmd.EncodingConfig

coreEndpoint string
coreConn *grpc.ClientConn
}

func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr string) (*Balance, error) {
queryCli := banktypes.NewQueryClient(ca.coreConn)

balReq := &banktypes.QueryBalanceRequest{
Address: addr,
Denom: app.DisplayDenom,
}

balResp, err := queryCli.Balance(ctx, balReq)
if err != nil {
return nil, err
}

return balResp.Balance, nil
}

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
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 @@ -60,7 +62,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 @@ -106,7 +108,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 @@ -95,7 +95,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

0 comments on commit 2d56c7d

Please sign in to comment.