From 63be6b7d06be3451db0e1aa104f758143b2f58e2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 14 Jun 2022 13:09:51 +0400 Subject: [PATCH 01/13] refactor: extract HeaderGetter interface from daser into header pkg --- header/interface.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/header/interface.go b/header/interface.go index b8debed8e0..c1752e94a9 100644 --- a/header/interface.go +++ b/header/interface.go @@ -116,3 +116,11 @@ type Getter interface { // GetRangeByHeight returns the given range [from:to) of ExtendedHeaders. GetRangeByHeight(ctx context.Context, from, to uint64) ([]*ExtendedHeader, error) } + +// Getter contains the behavior necessary for a component to retrieve +// headers that have been processed during header sync. +type Getter interface { + // GetByHeight returns the ExtendedHeader corresponding to the given + // block height. + GetByHeight(context.Context, uint64) (*ExtendedHeader, error) +} From 598cdd798e0671f6c68d7252b9e1766db20de9b5 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 14 Jun 2022 13:34:58 +0400 Subject: [PATCH 02/13] refactor: delete interface.go file from das pkg From d5869faa4d8c8c094a454be6bbb6ec3adfbd2b98 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 14 Jun 2022 17:51:34 +0400 Subject: [PATCH 03/13] refactor: Store interface wraps getter methods --- header/interface.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/header/interface.go b/header/interface.go index c1752e94a9..b8debed8e0 100644 --- a/header/interface.go +++ b/header/interface.go @@ -116,11 +116,3 @@ type Getter interface { // GetRangeByHeight returns the given range [from:to) of ExtendedHeaders. GetRangeByHeight(ctx context.Context, from, to uint64) ([]*ExtendedHeader, error) } - -// Getter contains the behavior necessary for a component to retrieve -// headers that have been processed during header sync. -type Getter interface { - // GetByHeight returns the ExtendedHeader corresponding to the given - // block height. - GetByHeight(context.Context, uint64) (*ExtendedHeader, error) -} From 91077f536407e0ce2f815d9d872693b614ac49b1 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 15 Jul 2022 14:21:38 +0200 Subject: [PATCH 04/13] chore: rebase fixes --- go.mod | 5 +- go.sum | 8 ++- node/components.go | 2 +- node/state/core.go | 10 ++-- node/state/state.go | 18 ++++-- service/state/core_access.go | 110 +++++++++++++++++++++++++++++++++-- service/state/interface.go | 27 +++++---- service/state/service.go | 6 +- 8 files changed, 154 insertions(+), 32 deletions(-) diff --git a/go.mod b/go.mod index cc9520bd16..a085ec72b9 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 @@ -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 diff --git a/go.sum b/go.sum index 0b0d41d68a..e79a1aee39 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= @@ -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= @@ -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= diff --git a/node/components.go b/node/components.go index 71b0fb06bf..ba282bd515 100644 --- a/node/components.go +++ b/node/components.go @@ -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)), ) diff --git a/node/state/core.go b/node/state/core.go index 0484f1df4d..804c7f07d2 100644 --- a/node/state/core.go +++ b/node/state/core.go @@ -4,6 +4,7 @@ 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" ) @@ -11,10 +12,11 @@ import ( // 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, diff --git a/node/state/state.go b/node/state/state.go index 67ff64ee6e..a3ac29f23b 100644 --- a/node/state/state.go +++ b/node/state/state.go @@ -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" @@ -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 { diff --git a/service/state/core_access.go b/service/state/core_access.go index ff8123fb25..e4d7766b76 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -4,15 +4,25 @@ import ( "context" "fmt" + "github.com/cosmos/cosmos-sdk/api/tendermint/abci" "github.com/cosmos/cosmos-sdk/types" + sdk_types "github.com/cosmos/cosmos-sdk/types" + sdk_errors "github.com/cosmos/cosmos-sdk/types/errors" sdk_tx "github.com/cosmos/cosmos-sdk/types/tx" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + bank_types "github.com/cosmos/cosmos-sdk/x/bank/types" + proof_utils "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" + sdk_abci "github.com/tendermint/tendermint/abci/types" + rpc_client "github.com/tendermint/tendermint/rpc/client" + "github.com/tendermint/tendermint/rpc/client/http" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/x/payment" apptypes "github.com/celestiaorg/celestia-app/x/payment/types" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/nmt/namespace" ) @@ -20,11 +30,15 @@ import ( // with a celestia-core node. type CoreAccessor struct { signer *apptypes.KeyringSigner + getter header.Getter + queryCli bank_types.QueryClient + rpcCli rpc_client.ABCIClient + + coreConn *grpc.ClientConn coreIP string + rpcPort string grpcPort string - coreConn *grpc.ClientConn - queryCli banktypes.QueryClient } // NewCoreAccessor dials the given celestia-core endpoint and @@ -32,12 +46,16 @@ type CoreAccessor struct { // connection. func NewCoreAccessor( signer *apptypes.KeyringSigner, + getter header.Getter, coreIP, + rpcPort string, grpcPort string, ) *CoreAccessor { return &CoreAccessor{ signer: signer, + getter: getter, coreIP: coreIP, + rpcPort: rpcPort, grpcPort: grpcPort, } } @@ -54,8 +72,14 @@ func (ca *CoreAccessor) Start(ctx context.Context) error { } ca.coreConn = client // create the query client - queryCli := banktypes.NewQueryClient(ca.coreConn) + queryCli := bank_types.NewQueryClient(ca.coreConn) ca.queryCli = queryCli + // create ABCI query client + cli, err := http.New(fmt.Sprintf("http://%s:%s", ca.coreIP, ca.rpcPort)) + if err != nil { + return err + } + ca.rpcCli = cli return nil } @@ -109,7 +133,7 @@ func (ca *CoreAccessor) Balance(ctx context.Context) (*Balance, error) { } func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { - req := &banktypes.QueryBalanceRequest{ + req := &bank_types.QueryBalanceRequest{ Address: addr.String(), Denom: app.BondDenom, } @@ -122,6 +146,67 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B return resp.Balance, nil } +func (ca *CoreAccessor) VerifiedBalance(ctx context.Context) (*Balance, error) { + addr, err := ca.signer.GetSignerInfo().GetAddress() + if err != nil { + return nil, err + } + return ca.VerifiedBalanceForAddress(ctx, addr) +} + +func (ca *CoreAccessor) VerifiedBalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { + head, err := ca.getter.Head(ctx) + if err != nil { + return nil, err + } + // construct an ABCI query for the height at head-1 because + // the AppHash contained in the head is actually the hash of + // the transactions contained in the previous blocks. + // TODO @renaynay: make PR on app to create convenience method for constructing this key + prefixedAccountKey := append(bank_types.CreateAccountBalancesPrefix(addr.Bytes()), []byte(app.BondDenom)...) + abciReq := abci.RequestQuery{ + // TODO @renayay: make PR on app to extract this into const + Path: fmt.Sprintf("store/%s/key", bank_types.StoreKey), + Height: head.Height - 1, + Data: prefixedAccountKey, + Prove: true, + } + opts := rpc_client.ABCIQueryOptions{ + Height: abciReq.Height, + Prove: abciReq.Prove, + } + result, err := ca.rpcCli.ABCIQueryWithOptions(ctx, abciReq.Path, abciReq.Data, opts) + if err != nil { + return nil, err + } + if !result.Response.IsOK() { + return nil, sdkErrorToGRPCError(result.Response) + } + // unmarshal balance information + value := result.Response.Value + coin, ok := sdk_types.NewIntFromString(string(value)) + if !ok { + return nil, fmt.Errorf("cannot convert %s into sdk_types.Int", string(value)) + } + // 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{, } + path := proof_utils.NewMerklePath(bank_types.StoreKey, string(prefixedAccountKey)) + err = merkleproof.VerifyMembership(proof_utils.GetSDKSpecs(), root, path, value) + if err != nil { + return nil, err + } + return &Balance{ + Denom: app.BondDenom, + Amount: coin, + }, nil +} + func (ca *CoreAccessor) SubmitTx(ctx context.Context, tx Tx) (*TxResponse, error) { txResp, err := apptypes.BroadcastTx(ctx, ca.coreConn, sdk_tx.BroadcastMode_BROADCAST_MODE_BLOCK, tx) if err != nil { @@ -157,10 +242,23 @@ func (ca *CoreAccessor) Transfer( return nil, err } coins := types.NewCoins(types.NewCoin(app.BondDenom, amount)) - msg := banktypes.NewMsgSend(from, to, coins) + msg := bank_types.NewMsgSend(from, to, coins) signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) if err != nil { return nil, err } return ca.SubmitTx(ctx, signedTx) } + +func sdkErrorToGRPCError(resp sdk_abci.ResponseQuery) error { + switch resp.Code { + case sdk_errors.ErrInvalidRequest.ABCICode(): + return status.Error(codes.InvalidArgument, resp.Log) + case sdk_errors.ErrUnauthorized.ABCICode(): + return status.Error(codes.Unauthenticated, resp.Log) + case sdk_errors.ErrKeyNotFound.ABCICode(): + return status.Error(codes.NotFound, resp.Log) + default: + return status.Error(codes.Unknown, resp.Log) + } +} diff --git a/service/state/interface.go b/service/state/interface.go index dc9eb02e37..4d5e4ce5f0 100644 --- a/service/state/interface.go +++ b/service/state/interface.go @@ -17,20 +17,27 @@ type Accessor interface { // Stop stops the state Accessor. Stop(context.Context) error - // SubmitPayForData builds, signs and submits a PayForData transaction. - SubmitPayForData(ctx context.Context, nID namespace.ID, data []byte, gasLim uint64) (*TxResponse, error) - - // Balance retrieves the Celestia coin balance - // for the node's account/signer. + // Balance retrieves the Celestia coin balance for the node's account/signer. Balance(ctx context.Context) (*Balance, error) - // BalanceForAddress retrieves the Celestia coin balance - // for the given types.AccAddress. + // BalanceForAddress retrieves the Celestia coin balance for the given types.AccAddress. BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) + // VerifiedBalance retrieves the Celestia coin balance for the node's account/signer + // and verifies it against the corresponding block's AppHash. + VerifiedBalance(ctx context.Context) (*Balance, error) + // VerifiedBalanceForAddress performs a balance request and verifies the returned balance against the + // corresponding block's AppHash. + // + // NOTE: the balance returned is technically the balance reported by the block right before + // the node's current head. This is due to the fact that for block N, the block's AppHash is + // the result of applying the previous block's transaction list. + VerifiedBalanceForAddress(ctx context.Context, addr Address) (*Balance, error) + + // Transfer sends the given amount of coins from default wallet of the node to the given account address. + Transfer(ctx context.Context, to types.Address, amount types.Int, gasLimit uint64) (*TxResponse, error) // SubmitTx submits the given transaction/message to the // Celestia network and blocks until the tx is included in // a block. SubmitTx(ctx context.Context, tx Tx) (*TxResponse, error) - - // Transfer sends the given amount of coins from default wallet of the node to the given account address. - Transfer(ctx context.Context, to types.Address, amount types.Int, gasLimit uint64) (*TxResponse, error) + // SubmitPayForData builds, signs and submits a PayForData transaction. + SubmitPayForData(ctx context.Context, nID namespace.ID, data []byte, gasLim uint64) (*TxResponse, error) } diff --git a/service/state/service.go b/service/state/service.go index b27b039f79..f982940422 100644 --- a/service/state/service.go +++ b/service/state/service.go @@ -3,6 +3,7 @@ package state import ( "context" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/nmt/namespace" ) @@ -13,12 +14,15 @@ type Service struct { cancel context.CancelFunc accessor Accessor + + getter header.Getter } // NewService constructs a new state Service. -func NewService(accessor Accessor) *Service { +func NewService(accessor Accessor, getter header.Getter) *Service { return &Service{ accessor: accessor, + getter: getter, } } From 9ed8a9c1bee1809edea1c8b0e76f1f28d6caadb8 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 14 Jul 2022 18:05:21 +0200 Subject: [PATCH 05/13] feat: add `/verified_balance` endpoint --- service/rpc/endpoints.go | 3 +++ service/rpc/state.go | 44 ++++++++++++++++++++++++++++++++++++---- service/state/service.go | 8 ++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/service/rpc/endpoints.go b/service/rpc/endpoints.go index 62b4764a2f..8bd5c5c6fb 100644 --- a/service/rpc/endpoints.go +++ b/service/rpc/endpoints.go @@ -10,6 +10,9 @@ func (h *Handler) RegisterEndpoints(rpc *Server) { rpc.RegisterHandlerFunc(balanceEndpoint, h.handleBalanceRequest, http.MethodGet) rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", balanceEndpoint, addrKey), h.handleBalanceForAddrRequest, http.MethodGet) + rpc.RegisterHandlerFunc(verifiedBalanceEndpoint, h.handleVerifiedBalanceRequest, http.MethodGet) + rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", verifiedBalanceEndpoint, addrKey), + h.handleVerifiedBalanceRequest, http.MethodGet) rpc.RegisterHandlerFunc(submitTxEndpoint, h.handleSubmitTx, http.MethodPost) rpc.RegisterHandlerFunc(submitPFDEndpoint, h.handleSubmitPFD, http.MethodPost) rpc.RegisterHandlerFunc(transferEndpoint, h.handleTransfer, http.MethodPost) diff --git a/service/rpc/state.go b/service/rpc/state.go index a44ce86ec1..6e3c2343f1 100644 --- a/service/rpc/state.go +++ b/service/rpc/state.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "github.com/celestiaorg/celestia-node/service/state" "net/http" "github.com/cosmos/cosmos-sdk/types" @@ -11,10 +12,11 @@ import ( ) const ( - balanceEndpoint = "/balance" - submitTxEndpoint = "/submit_tx" - submitPFDEndpoint = "/submit_pfd" - transferEndpoint = "/transfer" + balanceEndpoint = "/balance" + verifiedBalanceEndpoint = "/verified_balance" + submitTxEndpoint = "/submit_tx" + submitPFDEndpoint = "/submit_pfd" + transferEndpoint = "/transfer" ) var addrKey = "address" @@ -81,6 +83,40 @@ func (h *Handler) handleBalanceForAddrRequest(w http.ResponseWriter, r *http.Req } } +func (h *Handler) handleVerifiedBalanceRequest(w http.ResponseWriter, r *http.Request) { + var ( + bal *state.Balance + err error + ) + // read and parse request + vars := mux.Vars(r) + addrStr, exists := vars[addrKey] + if exists { + // convert address to Address type + addr, err := types.AccAddressFromBech32(addrStr) + if err != nil { + writeError(w, http.StatusBadRequest, verifiedBalanceEndpoint, err) + return + } + bal, err = h.state.VerifiedBalanceForAddress(r.Context(), addr) + } else { + bal, err = h.state.VerifiedBalance(r.Context()) + } + if err != nil { + writeError(w, http.StatusInternalServerError, verifiedBalanceEndpoint, err) + return + } + resp, err := json.Marshal(bal) + if err != nil { + writeError(w, http.StatusInternalServerError, verifiedBalanceEndpoint, err) + return + } + _, err = w.Write(resp) + if err != nil { + log.Errorw("writing response", "endpoint", verifiedBalanceEndpoint, "err", err) + } +} + func (h *Handler) handleSubmitTx(w http.ResponseWriter, r *http.Request) { // decode request var req submitTxRequest diff --git a/service/state/service.go b/service/state/service.go index f982940422..19164ce559 100644 --- a/service/state/service.go +++ b/service/state/service.go @@ -43,6 +43,14 @@ func (s *Service) BalanceForAddress(ctx context.Context, addr Address) (*Balance return s.accessor.BalanceForAddress(ctx, addr) } +func (s *Service) VerifiedBalance(ctx context.Context) (*Balance, error) { + return s.accessor.VerifiedBalance(ctx) +} + +func (s *Service) VerifiedBalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { + return s.accessor.VerifiedBalanceForAddress(ctx, addr) +} + func (s *Service) SubmitTx(ctx context.Context, tx Tx) (*TxResponse, error) { return s.accessor.SubmitTx(ctx, tx) } From a30b8f4da11637fa6979f5d37fedf91760d99f68 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 14 Jul 2022 18:06:41 +0200 Subject: [PATCH 06/13] chore: lint --- service/rpc/state.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/service/rpc/state.go b/service/rpc/state.go index 6e3c2343f1..5ff7d1d01d 100644 --- a/service/rpc/state.go +++ b/service/rpc/state.go @@ -4,11 +4,12 @@ import ( "encoding/hex" "encoding/json" "errors" - "github.com/celestiaorg/celestia-node/service/state" "net/http" "github.com/cosmos/cosmos-sdk/types" "github.com/gorilla/mux" + + "github.com/celestiaorg/celestia-node/service/state" ) const ( @@ -93,9 +94,9 @@ func (h *Handler) handleVerifiedBalanceRequest(w http.ResponseWriter, r *http.Re addrStr, exists := vars[addrKey] if exists { // convert address to Address type - addr, err := types.AccAddressFromBech32(addrStr) - if err != nil { - writeError(w, http.StatusBadRequest, verifiedBalanceEndpoint, err) + addr, addrerr := types.AccAddressFromBech32(addrStr) + if addrerr != nil { + writeError(w, http.StatusBadRequest, verifiedBalanceEndpoint, addrerr) return } bal, err = h.state.VerifiedBalanceForAddress(r.Context(), addr) From c01e554a72fc46cc4d196b299bc9e6588a6ae2b7 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 14 Jul 2022 18:09:49 +0200 Subject: [PATCH 07/13] refactor: dedup handleBalanceRequest --- service/rpc/endpoints.go | 2 +- service/rpc/state.go | 39 +++++++++++++++------------------------ 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/service/rpc/endpoints.go b/service/rpc/endpoints.go index 8bd5c5c6fb..ed99e18320 100644 --- a/service/rpc/endpoints.go +++ b/service/rpc/endpoints.go @@ -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(verifiedBalanceEndpoint, h.handleVerifiedBalanceRequest, http.MethodGet) rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", verifiedBalanceEndpoint, addrKey), diff --git a/service/rpc/state.go b/service/rpc/state.go index 5ff7d1d01d..92f0ff7bf6 100644 --- a/service/rpc/state.go +++ b/service/rpc/state.go @@ -42,33 +42,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 From 5937eaf9482b3e7319126495163d35d106ebb902 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 15 Jul 2022 16:55:48 +0200 Subject: [PATCH 08/13] docs: update state ADR --- docs/adr/adr-004-state-interaction.md | 51 +++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/docs/adr/adr-004-state-interaction.md b/docs/adr/adr-004-state-interaction.md index 6887487554..5a1e6d58c7 100644 --- a/docs/adr/adr-004-state-interaction.md +++ b/docs/adr/adr-004-state-interaction.md @@ -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.Store`'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{, } + 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 The `Syncer` in the `header` package provides one public method, `Finished()`, that indicates whether the syncer has From 55ffedfaaec3d988cc624273a5e1734f8e4b87ce Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 21 Jul 2022 13:35:27 +0200 Subject: [PATCH 09/13] refactor: consolidate verified_balance and balance endpoints --> verify balance by default --- service/rpc/endpoints.go | 3 --- service/rpc/state.go | 43 ++++-------------------------------- service/state/core_access.go | 22 ------------------ service/state/interface.go | 19 +++++++--------- service/state/service.go | 8 ------- 5 files changed, 12 insertions(+), 83 deletions(-) diff --git a/service/rpc/endpoints.go b/service/rpc/endpoints.go index ed99e18320..3ba7dbc1a7 100644 --- a/service/rpc/endpoints.go +++ b/service/rpc/endpoints.go @@ -10,9 +10,6 @@ func (h *Handler) RegisterEndpoints(rpc *Server) { rpc.RegisterHandlerFunc(balanceEndpoint, h.handleBalanceRequest, http.MethodGet) rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", balanceEndpoint, addrKey), h.handleBalanceRequest, http.MethodGet) - rpc.RegisterHandlerFunc(verifiedBalanceEndpoint, h.handleVerifiedBalanceRequest, http.MethodGet) - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", verifiedBalanceEndpoint, addrKey), - h.handleVerifiedBalanceRequest, http.MethodGet) rpc.RegisterHandlerFunc(submitTxEndpoint, h.handleSubmitTx, http.MethodPost) rpc.RegisterHandlerFunc(submitPFDEndpoint, h.handleSubmitPFD, http.MethodPost) rpc.RegisterHandlerFunc(transferEndpoint, h.handleTransfer, http.MethodPost) diff --git a/service/rpc/state.go b/service/rpc/state.go index 92f0ff7bf6..998c5bfc2b 100644 --- a/service/rpc/state.go +++ b/service/rpc/state.go @@ -13,11 +13,10 @@ import ( ) const ( - balanceEndpoint = "/balance" - verifiedBalanceEndpoint = "/verified_balance" - submitTxEndpoint = "/submit_tx" - submitPFDEndpoint = "/submit_pfd" - transferEndpoint = "/transfer" + balanceEndpoint = "/balance" + submitTxEndpoint = "/submit_tx" + submitPFDEndpoint = "/submit_pfd" + transferEndpoint = "/transfer" ) var addrKey = "address" @@ -75,40 +74,6 @@ func (h *Handler) handleBalanceRequest(w http.ResponseWriter, r *http.Request) { } } -func (h *Handler) handleVerifiedBalanceRequest(w http.ResponseWriter, r *http.Request) { - var ( - bal *state.Balance - err error - ) - // read and parse request - vars := mux.Vars(r) - addrStr, exists := vars[addrKey] - if exists { - // convert address to Address type - addr, addrerr := types.AccAddressFromBech32(addrStr) - if addrerr != nil { - writeError(w, http.StatusBadRequest, verifiedBalanceEndpoint, addrerr) - return - } - bal, err = h.state.VerifiedBalanceForAddress(r.Context(), addr) - } else { - bal, err = h.state.VerifiedBalance(r.Context()) - } - if err != nil { - writeError(w, http.StatusInternalServerError, verifiedBalanceEndpoint, err) - return - } - resp, err := json.Marshal(bal) - if err != nil { - writeError(w, http.StatusInternalServerError, verifiedBalanceEndpoint, err) - return - } - _, err = w.Write(resp) - if err != nil { - log.Errorw("writing response", "endpoint", verifiedBalanceEndpoint, "err", err) - } -} - func (h *Handler) handleSubmitTx(w http.ResponseWriter, r *http.Request) { // decode request var req submitTxRequest diff --git a/service/state/core_access.go b/service/state/core_access.go index e4d7766b76..d475a91b9e 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -133,28 +133,6 @@ func (ca *CoreAccessor) Balance(ctx context.Context) (*Balance, error) { } func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { - req := &bank_types.QueryBalanceRequest{ - Address: addr.String(), - Denom: app.BondDenom, - } - - resp, err := ca.queryCli.Balance(ctx, req) - if err != nil { - return nil, fmt.Errorf("querying client for balance: %s", err.Error()) - } - - return resp.Balance, nil -} - -func (ca *CoreAccessor) VerifiedBalance(ctx context.Context) (*Balance, error) { - addr, err := ca.signer.GetSignerInfo().GetAddress() - if err != nil { - return nil, err - } - return ca.VerifiedBalanceForAddress(ctx, addr) -} - -func (ca *CoreAccessor) VerifiedBalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { head, err := ca.getter.Head(ctx) if err != nil { return nil, err diff --git a/service/state/interface.go b/service/state/interface.go index 4d5e4ce5f0..dfdb8b4389 100644 --- a/service/state/interface.go +++ b/service/state/interface.go @@ -17,20 +17,17 @@ type Accessor interface { // Stop stops the state Accessor. Stop(context.Context) error - // Balance retrieves the Celestia coin balance for the node's account/signer. + // Balance retrieves the Celestia coin balance for the node's account/signer + // and verifies it against the corresponding block's AppHash. Balance(ctx context.Context) (*Balance, error) // BalanceForAddress retrieves the Celestia coin balance for the given types.AccAddress. - BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) - // VerifiedBalance retrieves the Celestia coin balance for the node's account/signer - // and verifies it against the corresponding block's AppHash. - VerifiedBalance(ctx context.Context) (*Balance, error) - // VerifiedBalanceForAddress performs a balance request and verifies the returned balance against the - // corresponding block's AppHash. + // BalanceForAddress retrieves the Celestia coin balance for the given address and verifies + // the returned balance against the corresponding block's AppHash. // - // NOTE: the balance returned is technically the balance reported by the block right before - // the node's current head. This is due to the fact that for block N, the block's AppHash is - // the result of applying the previous block's transaction list. - VerifiedBalanceForAddress(ctx context.Context, addr Address) (*Balance, error) + // NOTE: the balance returned is the balance reported by the block right before + // the node's current head (head-1). This is due to the fact that for block N, the block's + // `AppHash` is the result of applying the previous block's transaction list. + BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) // Transfer sends the given amount of coins from default wallet of the node to the given account address. Transfer(ctx context.Context, to types.Address, amount types.Int, gasLimit uint64) (*TxResponse, error) diff --git a/service/state/service.go b/service/state/service.go index 19164ce559..f982940422 100644 --- a/service/state/service.go +++ b/service/state/service.go @@ -43,14 +43,6 @@ func (s *Service) BalanceForAddress(ctx context.Context, addr Address) (*Balance return s.accessor.BalanceForAddress(ctx, addr) } -func (s *Service) VerifiedBalance(ctx context.Context) (*Balance, error) { - return s.accessor.VerifiedBalance(ctx) -} - -func (s *Service) VerifiedBalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { - return s.accessor.VerifiedBalanceForAddress(ctx, addr) -} - func (s *Service) SubmitTx(ctx context.Context, tx Tx) (*TxResponse, error) { return s.accessor.SubmitTx(ctx, tx) } From 5b47c09c14a6ee5ca8b47b0d97af60985bc53daf Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 22 Jul 2022 15:56:58 +0200 Subject: [PATCH 10/13] chore: replace TODOs with PR refs --- service/state/core_access.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/state/core_access.go b/service/state/core_access.go index d475a91b9e..431d0bb8e6 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -140,10 +140,10 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B // construct an ABCI query for the height at head-1 because // the AppHash contained in the head is actually the hash of // the transactions contained in the previous blocks. - // TODO @renaynay: make PR on app to create convenience method for constructing this key + // TODO @renaynay: once https://github.com/cosmos/cosmos-sdk/pull/12674 is merged, use this method instead prefixedAccountKey := append(bank_types.CreateAccountBalancesPrefix(addr.Bytes()), []byte(app.BondDenom)...) abciReq := abci.RequestQuery{ - // TODO @renayay: make PR on app to extract this into const + // TODO @renayay: once https://github.com/cosmos/cosmos-sdk/pull/12674 is merged, use const instead Path: fmt.Sprintf("store/%s/key", bank_types.StoreKey), Height: head.Height - 1, Data: prefixedAccountKey, From 3fd9f6ca4ec864f971c46e6e12c878ec8750b82f Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 25 Jul 2022 15:58:41 +0200 Subject: [PATCH 11/13] refactor: apply john review comments --- docs/adr/adr-004-state-interaction.md | 62 +++++++++++++-------------- service/state/core_access.go | 4 +- service/state/interface.go | 1 - 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/docs/adr/adr-004-state-interaction.md b/docs/adr/adr-004-state-interaction.md index 5a1e6d58c7..0be6faa748 100644 --- a/docs/adr/adr-004-state-interaction.md +++ b/docs/adr/adr-004-state-interaction.md @@ -69,29 +69,29 @@ 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 celestia-app and verify them against the latest head's `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.Store`'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 - } + 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 @@ -103,19 +103,19 @@ Using `types.ConvertProofs()` returns a `types.MerkleProof` that wraps a chain o 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{, } - 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 - } + // 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{, } + 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 diff --git a/service/state/core_access.go b/service/state/core_access.go index 431d0bb8e6..2919f78cfa 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -138,8 +138,8 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B return nil, err } // construct an ABCI query for the height at head-1 because - // the AppHash contained in the head is actually the hash of - // the transactions contained in the previous blocks. + // the AppHash contained in the head is actually the state root + // after applying the transactions contained in the previous block. // TODO @renaynay: once https://github.com/cosmos/cosmos-sdk/pull/12674 is merged, use this method instead prefixedAccountKey := append(bank_types.CreateAccountBalancesPrefix(addr.Bytes()), []byte(app.BondDenom)...) abciReq := abci.RequestQuery{ diff --git a/service/state/interface.go b/service/state/interface.go index dfdb8b4389..2f2a53ccd8 100644 --- a/service/state/interface.go +++ b/service/state/interface.go @@ -20,7 +20,6 @@ type Accessor interface { // Balance retrieves the Celestia coin balance for the node's account/signer // and verifies it against the corresponding block's AppHash. Balance(ctx context.Context) (*Balance, error) - // BalanceForAddress retrieves the Celestia coin balance for the given types.AccAddress. // BalanceForAddress retrieves the Celestia coin balance for the given address and verifies // the returned balance against the corresponding block's AppHash. // From 2e3ccd5c13406597f42696e51d349d83df3aafe2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 26 Jul 2022 13:14:51 +0200 Subject: [PATCH 12/13] refactor: extract sdk error func into helpers file, rename import aliases --- service/state/core_access.go | 57 +++++++++++++----------------------- service/state/helpers.go | 21 +++++++++++++ 2 files changed, 41 insertions(+), 37 deletions(-) create mode 100644 service/state/helpers.go diff --git a/service/state/core_access.go b/service/state/core_access.go index 2919f78cfa..5aa9943daf 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -6,18 +6,14 @@ import ( "github.com/cosmos/cosmos-sdk/api/tendermint/abci" "github.com/cosmos/cosmos-sdk/types" - sdk_types "github.com/cosmos/cosmos-sdk/types" - sdk_errors "github.com/cosmos/cosmos-sdk/types/errors" - sdk_tx "github.com/cosmos/cosmos-sdk/types/tx" - bank_types "github.com/cosmos/cosmos-sdk/x/bank/types" - proof_utils "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" - sdk_abci "github.com/tendermint/tendermint/abci/types" - rpc_client "github.com/tendermint/tendermint/rpc/client" + sdktypes "github.com/cosmos/cosmos-sdk/types" + sdktx "github.com/cosmos/cosmos-sdk/types/tx" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + proofutils "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" + rpcclient "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/client/http" "google.golang.org/grpc" - "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/status" "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/x/payment" @@ -32,8 +28,8 @@ type CoreAccessor struct { signer *apptypes.KeyringSigner getter header.Getter - queryCli bank_types.QueryClient - rpcCli rpc_client.ABCIClient + queryCli banktypes.QueryClient + rpcCli rpcclient.ABCIClient coreConn *grpc.ClientConn coreIP string @@ -72,7 +68,7 @@ func (ca *CoreAccessor) Start(ctx context.Context) error { } ca.coreConn = client // create the query client - queryCli := bank_types.NewQueryClient(ca.coreConn) + queryCli := banktypes.NewQueryClient(ca.coreConn) ca.queryCli = queryCli // create ABCI query client cli, err := http.New(fmt.Sprintf("http://%s:%s", ca.coreIP, ca.rpcPort)) @@ -141,15 +137,15 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B // the AppHash contained in the head is actually the state root // after applying the transactions contained in the previous block. // TODO @renaynay: once https://github.com/cosmos/cosmos-sdk/pull/12674 is merged, use this method instead - prefixedAccountKey := append(bank_types.CreateAccountBalancesPrefix(addr.Bytes()), []byte(app.BondDenom)...) + prefixedAccountKey := append(banktypes.CreateAccountBalancesPrefix(addr.Bytes()), []byte(app.BondDenom)...) abciReq := abci.RequestQuery{ // TODO @renayay: once https://github.com/cosmos/cosmos-sdk/pull/12674 is merged, use const instead - Path: fmt.Sprintf("store/%s/key", bank_types.StoreKey), + Path: fmt.Sprintf("store/%s/key", banktypes.StoreKey), Height: head.Height - 1, Data: prefixedAccountKey, Prove: true, } - opts := rpc_client.ABCIQueryOptions{ + opts := rpcclient.ABCIQueryOptions{ Height: abciReq.Height, Prove: abciReq.Prove, } @@ -162,20 +158,20 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B } // unmarshal balance information value := result.Response.Value - coin, ok := sdk_types.NewIntFromString(string(value)) + coin, ok := sdktypes.NewIntFromString(string(value)) if !ok { - return nil, fmt.Errorf("cannot convert %s into sdk_types.Int", string(value)) + return nil, fmt.Errorf("cannot convert %s into sdktypes.Int", string(value)) } // convert proofs into a more digestible format - merkleproof, err := proof_utils.ConvertProofs(result.Response.GetProofOps()) + merkleproof, err := proofutils.ConvertProofs(result.Response.GetProofOps()) if err != nil { return nil, err } - root := proof_utils.NewMerkleRoot(head.AppHash) + root := proofutils.NewMerkleRoot(head.AppHash) // VerifyMembership expects the path as: // []string{, } - path := proof_utils.NewMerklePath(bank_types.StoreKey, string(prefixedAccountKey)) - err = merkleproof.VerifyMembership(proof_utils.GetSDKSpecs(), root, path, value) + path := proofutils.NewMerklePath(banktypes.StoreKey, string(prefixedAccountKey)) + err = merkleproof.VerifyMembership(proofutils.GetSDKSpecs(), root, path, value) if err != nil { return nil, err } @@ -186,7 +182,7 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B } func (ca *CoreAccessor) SubmitTx(ctx context.Context, tx Tx) (*TxResponse, error) { - txResp, err := apptypes.BroadcastTx(ctx, ca.coreConn, sdk_tx.BroadcastMode_BROADCAST_MODE_BLOCK, tx) + txResp, err := apptypes.BroadcastTx(ctx, ca.coreConn, sdktx.BroadcastMode_BROADCAST_MODE_BLOCK, tx) if err != nil { return nil, err } @@ -196,7 +192,7 @@ func (ca *CoreAccessor) SubmitTx(ctx context.Context, tx Tx) (*TxResponse, error func (ca *CoreAccessor) SubmitTxWithBroadcastMode( ctx context.Context, tx Tx, - mode sdk_tx.BroadcastMode, + mode sdktx.BroadcastMode, ) (*TxResponse, error) { txResp, err := apptypes.BroadcastTx(ctx, ca.coreConn, mode, tx) if err != nil { @@ -220,23 +216,10 @@ func (ca *CoreAccessor) Transfer( return nil, err } coins := types.NewCoins(types.NewCoin(app.BondDenom, amount)) - msg := bank_types.NewMsgSend(from, to, coins) + msg := banktypes.NewMsgSend(from, to, coins) signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) if err != nil { return nil, err } return ca.SubmitTx(ctx, signedTx) } - -func sdkErrorToGRPCError(resp sdk_abci.ResponseQuery) error { - switch resp.Code { - case sdk_errors.ErrInvalidRequest.ABCICode(): - return status.Error(codes.InvalidArgument, resp.Log) - case sdk_errors.ErrUnauthorized.ABCICode(): - return status.Error(codes.Unauthenticated, resp.Log) - case sdk_errors.ErrKeyNotFound.ABCICode(): - return status.Error(codes.NotFound, resp.Log) - default: - return status.Error(codes.Unknown, resp.Log) - } -} diff --git a/service/state/helpers.go b/service/state/helpers.go new file mode 100644 index 0000000000..db6314e5bf --- /dev/null +++ b/service/state/helpers.go @@ -0,0 +1,21 @@ +package state + +import ( + sdk_errors "github.com/cosmos/cosmos-sdk/types/errors" + sdk_abci "github.com/tendermint/tendermint/abci/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func sdkErrorToGRPCError(resp sdk_abci.ResponseQuery) error { + switch resp.Code { + case sdk_errors.ErrInvalidRequest.ABCICode(): + return status.Error(codes.InvalidArgument, resp.Log) + case sdk_errors.ErrUnauthorized.ABCICode(): + return status.Error(codes.Unauthenticated, resp.Log) + case sdk_errors.ErrKeyNotFound.ABCICode(): + return status.Error(codes.NotFound, resp.Log) + default: + return status.Error(codes.Unknown, resp.Log) + } +} From 864f100a1e92cfed653a7ffdab9720cf183963e5 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 27 Jul 2022 15:32:39 +0200 Subject: [PATCH 13/13] doc: update ADR header.Store -> header.Getter --- docs/adr/adr-004-state-interaction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/adr/adr-004-state-interaction.md b/docs/adr/adr-004-state-interaction.md index 0be6faa748..7c124d5810 100644 --- a/docs/adr/adr-004-state-interaction.md +++ b/docs/adr/adr-004-state-interaction.md @@ -71,7 +71,7 @@ type StateAccessor interface { In order to check that the balances returned via the `AccountBalance` query are correct, it is necessary to also request 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.Store`'s `Head()` method in order to get the latest known header of the node and check its `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