diff --git a/docs/adr/adr-004-state-interaction.md b/docs/adr/adr-004-state-interaction.md index 6887487554..7c124d5810 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.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{, } + 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 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/rpc/endpoints.go b/service/rpc/endpoints.go index 62b4764a2f..3ba7dbc1a7 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(submitTxEndpoint, h.handleSubmitTx, http.MethodPost) rpc.RegisterHandlerFunc(submitPFDEndpoint, h.handleSubmitPFD, http.MethodPost) diff --git a/service/rpc/state.go b/service/rpc/state.go index a44ce86ec1..998c5bfc2b 100644 --- a/service/rpc/state.go +++ b/service/rpc/state.go @@ -8,6 +8,8 @@ import ( "github.com/cosmos/cosmos-sdk/types" "github.com/gorilla/mux" + + "github.com/celestiaorg/celestia-node/service/state" ) const ( @@ -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 diff --git a/service/state/core_access.go b/service/state/core_access.go index ff8123fb25..5aa9943daf 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -4,15 +4,21 @@ import ( "context" "fmt" + "github.com/cosmos/cosmos-sdk/api/tendermint/abci" "github.com/cosmos/cosmos-sdk/types" - sdk_tx "github.com/cosmos/cosmos-sdk/types/tx" + 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/credentials/insecure" "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 +26,15 @@ import ( // with a celestia-core node. type CoreAccessor struct { signer *apptypes.KeyringSigner + getter header.Getter + queryCli banktypes.QueryClient + rpcCli rpcclient.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 +42,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, } } @@ -56,6 +70,12 @@ func (ca *CoreAccessor) Start(ctx context.Context) error { // create the query client 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)) + if err != nil { + return err + } + ca.rpcCli = cli return nil } @@ -109,21 +129,60 @@ func (ca *CoreAccessor) Balance(ctx context.Context) (*Balance, error) { } func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { - req := &banktypes.QueryBalanceRequest{ - Address: addr.String(), - Denom: app.BondDenom, + head, err := ca.getter.Head(ctx) + if err != nil { + return nil, err } - - resp, err := ca.queryCli.Balance(ctx, req) + // construct an ABCI query for the height at head-1 because + // 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(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", banktypes.StoreKey), + Height: head.Height - 1, + Data: prefixedAccountKey, + Prove: true, + } + opts := rpcclient.ABCIQueryOptions{ + Height: abciReq.Height, + Prove: abciReq.Prove, + } + result, err := ca.rpcCli.ABCIQueryWithOptions(ctx, abciReq.Path, abciReq.Data, opts) if err != nil { - return nil, fmt.Errorf("querying client for balance: %s", err.Error()) + return nil, err } - - return resp.Balance, nil + if !result.Response.IsOK() { + return nil, sdkErrorToGRPCError(result.Response) + } + // unmarshal balance information + value := result.Response.Value + coin, ok := sdktypes.NewIntFromString(string(value)) + if !ok { + return nil, fmt.Errorf("cannot convert %s into sdktypes.Int", string(value)) + } + // convert proofs into a more digestible format + merkleproof, err := proofutils.ConvertProofs(result.Response.GetProofOps()) + if err != nil { + return nil, err + } + root := proofutils.NewMerkleRoot(head.AppHash) + // VerifyMembership expects the path as: + // []string{, } + path := proofutils.NewMerklePath(banktypes.StoreKey, string(prefixedAccountKey)) + err = merkleproof.VerifyMembership(proofutils.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) + txResp, err := apptypes.BroadcastTx(ctx, ca.coreConn, sdktx.BroadcastMode_BROADCAST_MODE_BLOCK, tx) if err != nil { return nil, err } @@ -133,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 { 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) + } +} diff --git a/service/state/interface.go b/service/state/interface.go index dc9eb02e37..2f2a53ccd8 100644 --- a/service/state/interface.go +++ b/service/state/interface.go @@ -17,20 +17,23 @@ 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 + // 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. + // + // 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) // 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, } }