diff --git a/node/node.go b/node/node.go index 96fa8b967e..a1f37f7a1b 100644 --- a/node/node.go +++ b/node/node.go @@ -52,7 +52,8 @@ type Node struct { DataExchange exchange.Interface DAG format.DAGService // p2p protocols - PubSub *pubsub.PubSub + PubSub *pubsub.PubSub + // services ShareServ share.Service // not optional HeaderServ *header.Service // not optional StateServ *state.Service // not optional diff --git a/service/state/core_access.go b/service/state/core_access.go index 0796af7999..5320d17794 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -103,3 +103,14 @@ func (ca *CoreAccessor) SubmitTxWithBroadcastMode( } return txResp.TxResponse, nil } + +func (ca *CoreAccessor) KeyringSigner() *apptypes.KeyringSigner { + return ca.signer +} + +func (ca *CoreAccessor) Conn() (*grpc.ClientConn, error) { + if ca.coreConn == nil { + return nil, fmt.Errorf("no running gRPC connection") + } + return ca.coreConn, nil +} diff --git a/service/state/endpoints.md b/service/state/endpoints.md deleted file mode 100644 index d069fb9787..0000000000 --- a/service/state/endpoints.md +++ /dev/null @@ -1,37 +0,0 @@ -# Celestia-Node State API - -### `GET` `/balance` - -Request -``` -curl -X GET http://:26658/balance -``` - -Response -```json -{"denom":"uceles","amount":"999995000000000"} -``` - -### `POST` `/balance/{address}` - -Request -``` -curl -X GET http://:26658/balance/celes1vuw7pdcyap2u62x6dyqjdzznlg25jll6jj76d0 -``` - -Response -```json -{"denom":"uceles","amount":"999995000000000"} -``` - -### `POST` `/submit_tx/{tx}` - -Request -``` -curl -X POST http://:26658/submit_tx/0A84060A81060A1D2F7061796D656E742E4D736757697265506179466F724D65737361676512DF050A2C63656C6573316D7672716D6C65676C347573646B74616777617A7836307367723071746D67386A783876363312080101010101010101188002228002010203040506070809000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003266081012209E3D0CA95E762AF12F0E841B898A5C4954253C1DD27D74BDD4BD27E1DFE895071A408D22453C262C8EC16806977B4A710F68F857B6D4F22D36DD2B256D1F5F94FFCF686BC42B65F22EB51D3EEEE5A1128D6E219955D8A43A6EC7985F9A6AD7D05EFA3266082012209E3D0CA95E762AF12F0E841B898A5C4954253C1DD27D74BDD4BD27E1DFE895071A408D22453C262C8EC16806977B4A710F68F857B6D4F22D36DD2B256D1F5F94FFCF686BC42B65F22EB51D3EEEE5A1128D6E219955D8A43A6EC7985F9A6AD7D05EFA3266084012209E3D0CA95E762AF12F0E841B898A5C4954253C1DD27D74BDD4BD27E1DFE895071A408D22453C262C8EC16806977B4A710F68F857B6D4F22D36DD2B256D1F5F94FFCF686BC42B65F22EB51D3EEEE5A1128D6E219955D8A43A6EC7985F9A6AD7D05EFA326708800112209E3D0CA95E762AF12F0E841B898A5C4954253C1DD27D74BDD4BD27E1DFE895071A408D22453C262C8EC16806977B4A710F68F857B6D4F22D36DD2B256D1F5F94FFCF686BC42B65F22EB51D3EEEE5A1128D6E219955D8A43A6EC7985F9A6AD7D05EFA12580A500A460A1F2F636F736D6F732E63727970746F2E736563703235366B312E5075624B657912230A21037C880843C4E796D1AEDB49F6D2F639B5A8D2B4F0AF3C95577064B7939727683E12040A0208011803120410C09A0C1A404F3C4D7841233B9023577DFA3897D9F926FDAA80292DFF34E54E6897450A54A60C892A2B545B0EAB0DE88FFEC5A490FED1318858B143348438B103FA38B8F13B -``` - -Response -```json -{"txhash":"E4B98AF73852A2FC99E1708C3C5B7CE93449A0E9381387D022FAE759F76E1F0D","codespace":"sdk","code":19,"logs":null} -``` \ No newline at end of file diff --git a/service/state/interface.go b/service/state/interface.go index 05de4b8b3b..4e03fc2d38 100644 --- a/service/state/interface.go +++ b/service/state/interface.go @@ -2,6 +2,10 @@ package state import ( "context" + + "google.golang.org/grpc" + + apptypes "github.com/celestiaorg/celestia-app/x/payment/types" ) // Accessor represents the behaviors necessary for a user to @@ -12,6 +16,13 @@ type Accessor interface { Start(context.Context) error // Stop stops the state Accessor. Stop(context.Context) error + + // KeyringSigner returns the KeyringSigner used by the Accessor. + KeyringSigner() *apptypes.KeyringSigner + // Conn returns a gRPC connection to node that serves state-related + // requests. + Conn() (*grpc.ClientConn, error) + // Balance retrieves the Celestia coin balance // for the node's account/signer. Balance(ctx context.Context) (*Balance, error) diff --git a/service/state/pfd.go b/service/state/pfd.go new file mode 100644 index 0000000000..ed736ec502 --- /dev/null +++ b/service/state/pfd.go @@ -0,0 +1,101 @@ +package state + +import ( + "context" + "fmt" + + "github.com/cosmos/cosmos-sdk/x/auth/signing" + + apptypes "github.com/celestiaorg/celestia-app/x/payment/types" + "github.com/celestiaorg/nmt/namespace" +) + +var ( + // shareSizes includes all the possible share sizes of the given data + // that the signer must sign over. + shareSizes = []uint64{16, 32, 64, 128} +) + +// PayForData is an alias to the PayForMessage packet. +type PayForData = apptypes.MsgWirePayForMessage + +// SubmitPayForData builds, signs and submits a PayForData message. +func (s *Service) SubmitPayForData( + ctx context.Context, + nID namespace.ID, + data []byte, + gasLim uint64, + maxSize uint64, +) (*TxResponse, error) { + pfd, err := s.BuildPayForData(ctx, nID, data, gasLim, maxSize) + if err != nil { + return nil, err + } + + signed, err := s.SignPayForData(pfd, apptypes.SetGasLimit(gasLim)) + if err != nil { + return nil, err + } + + signer := s.accessor.KeyringSigner() + rawTx, err := signer.EncodeTx(signed) + if err != nil { + return nil, err + } + + return s.accessor.SubmitTx(ctx, rawTx) +} + +// BuildPayForData builds a PayForData message. +func (s *Service) BuildPayForData( + ctx context.Context, + nID namespace.ID, + message []byte, + gasLim uint64, + maxSize uint64, +) (*PayForData, error) { + // create the raw WirePayForMessage transaction + wpfmMsg, err := apptypes.NewWirePayForMessage(nID, message, shareSizes...) + if err != nil { + return nil, err + } + // TODO @renaynay: as a result of `padMessage`, the following is observed: + // {wpfmMsg.MessageSize: 256, len(message): 5} + if wpfmMsg.GetMessageSize() > maxSize { + return nil, fmt.Errorf("message size %d cannot exceed specified max size %d", wpfmMsg.GetMessageSize(), maxSize) + } + + // get signer and conn info + signer := s.accessor.KeyringSigner() + conn, err := s.accessor.Conn() + if err != nil { + return nil, err + } + + // query for account information necessary to sign a valid tx + err = signer.QueryAccountNumber(ctx, conn) + if err != nil { + return nil, err + } + + // generate the signatures for each `MsgPayForMessage` using the `KeyringSigner`, + // then set the gas limit for the tx + gasLimOption := apptypes.SetGasLimit(gasLim) + err = wpfmMsg.SignShareCommitments(signer, gasLimOption) + if err != nil { + return nil, err + } + + return wpfmMsg, nil +} + +// SignPayForData signs the given PayForData message. +func (s *Service) SignPayForData(pfd *PayForData, gasLimOption apptypes.TxBuilderOption) (signing.Tx, error) { + signer := s.accessor.KeyringSigner() + // Build and sign the final `WirePayForMessage` tx that now contains the signatures + // for potential `MsgPayForMessage`s + return signer.BuildSignedTx( + gasLimOption(signer.NewTxBuilder()), + pfd, + ) +} diff --git a/service/state/rpc.go b/service/state/rpc.go index 57eb69353c..70bb34c847 100644 --- a/service/state/rpc.go +++ b/service/state/rpc.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "io/ioutil" "net/http" "github.com/cosmos/cosmos-sdk/types" @@ -13,17 +14,25 @@ import ( "github.com/celestiaorg/celestia-node/node/rpc" ) -var log = logging.Logger("state/rpc") - var ( addrKey = "address" txKey = "tx" + + log = logging.Logger("state/rpc") ) +type submitPFDRequest struct { + NamespaceID string `json:"namespace_id"` + Data string `json:"data"` + GasLimit uint64 `json:"gas_limit"` + MaxDataSize uint64 `json:"max_data_size"` +} + func (s *Service) RegisterEndpoints(rpc *rpc.Server) { rpc.RegisterHandlerFunc("/balance", s.handleBalanceRequest, http.MethodGet) rpc.RegisterHandlerFunc(fmt.Sprintf("/balance/{%s}", addrKey), s.handleBalanceForAddrRequest, http.MethodGet) rpc.RegisterHandlerFunc(fmt.Sprintf("/submit_tx/{%s}", txKey), s.handleSubmitTx, http.MethodPost) + rpc.RegisterHandlerFunc("/submit_pfd", s.handleSubmitPFD, http.MethodPost) } func (s *Service) handleBalanceRequest(w http.ResponseWriter, r *http.Request) { @@ -101,3 +110,51 @@ func (s *Service) handleSubmitTx(w http.ResponseWriter, r *http.Request) { log.Errorw("writing /balance response", "err", err) } } + +func (s *Service) handleSubmitPFD(w http.ResponseWriter, r *http.Request) { + // parse body from request + raw, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + log.Errorw("serving /submit_pfd request", "err", err) + return + } + // decode request + req := new(submitPFDRequest) + err = json.Unmarshal(raw, req) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + log.Errorw("serving /submit_pfd request", "err", err) + return + } + + nID, err := hex.DecodeString(req.NamespaceID) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + log.Errorw("serving /submit_pfd request", "err", err) + return + } + data := []byte(req.Data) + + // perform request + txResp, err := s.SubmitPayForData(r.Context(), nID, data, req.GasLimit, req.MaxDataSize) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + _, werr := w.Write([]byte(err.Error())) + if werr != nil { + log.Errorw("writing /submit_pfd response", "err", werr) + } + log.Errorw("serving /submit_pfd request", "err", err) + return + } + resp, err := json.Marshal(txResp) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Errorw("serving /submit_pfd request", "err", err) + return + } + _, err = w.Write(resp) + if err != nil { + log.Errorw("writing /submit_pfd response", "err", err) + } +} diff --git a/service/state/state.go b/service/state/state.go index 74e50ae99b..bd1b4d587f 100644 --- a/service/state/state.go +++ b/service/state/state.go @@ -8,7 +8,6 @@ import ( // Balance is an alias to the Coin type from Cosmos-SDK. type Balance = sdk.Coin -// Tx is an alias to the Tx type from celestia-core. type Tx = coretypes.Tx // TxResponse is an alias to the TxResponse type from Cosmos-SDK.