diff --git a/da/celestia/celestia.go b/da/celestia/celestia.go new file mode 100644 index 0000000000..72c891ce0e --- /dev/null +++ b/da/celestia/celestia.go @@ -0,0 +1,147 @@ +package celestia + +import ( + "context" + "encoding/json" + "time" + + "github.com/gogo/protobuf/proto" + + "github.com/celestiaorg/optimint/da" + "github.com/celestiaorg/optimint/libs/cnrc" + "github.com/celestiaorg/optimint/log" + "github.com/celestiaorg/optimint/store" + "github.com/celestiaorg/optimint/types" + pb "github.com/celestiaorg/optimint/types/pb/optimint" +) + +// DataAvailabilityLayerClient use celestia-node public API. +type DataAvailabilityLayerClient struct { + client *cnrc.Client + + config Config + logger log.Logger +} + +var _ da.DataAvailabilityLayerClient = &DataAvailabilityLayerClient{} +var _ da.BlockRetriever = &DataAvailabilityLayerClient{} + +type Config struct { + BaseURL string `json:"base_url"` + Timeout time.Duration `json:"timeout"` + GasLimit uint64 `json:"gas_limit"` + NamespaceID [8]byte `json:"namespace_id"` +} + +func (c *DataAvailabilityLayerClient) Init(config []byte, kvStore store.KVStore, logger log.Logger) error { + c.logger = logger + + if len(config) > 0 { + return json.Unmarshal(config, &c.config) + } + + return nil +} + +func (c *DataAvailabilityLayerClient) Start() error { + c.logger.Info("starting Celestia Data Availability Layer Client", "baseURL", c.config.BaseURL) + var err error + c.client, err = cnrc.NewClient(c.config.BaseURL, cnrc.WithTimeout(c.config.Timeout)) + return err +} + +func (c *DataAvailabilityLayerClient) Stop() error { + c.logger.Info("stopping Celestia Data Availability Layer Client") + return nil +} + +func (c *DataAvailabilityLayerClient) SubmitBlock(block *types.Block) da.ResultSubmitBlock { + blob, err := block.MarshalBinary() + if err != nil { + return da.ResultSubmitBlock{ + DAResult: da.DAResult{ + Code: da.StatusError, + Message: err.Error(), + }, + } + } + + txResponse, err := c.client.SubmitPFD(context.TODO(), c.config.NamespaceID, blob, c.config.GasLimit) + + if err != nil { + return da.ResultSubmitBlock{ + DAResult: da.DAResult{ + Code: da.StatusError, + Message: err.Error(), + }, + } + } + + return da.ResultSubmitBlock{ + DAResult: da.DAResult{ + Code: da.StatusSuccess, + Message: "tx hash: " + txResponse.TxHash, + DAHeight: uint64(txResponse.Height), + }, + } +} + +func (c *DataAvailabilityLayerClient) CheckBlockAvailability(dataLayerHeight uint64) da.ResultCheckBlock { + shares, err := c.client.NamespacedShares(context.TODO(), c.config.NamespaceID, dataLayerHeight) + if err != nil { + return da.ResultCheckBlock{ + DAResult: da.DAResult{ + Code: da.StatusError, + Message: err.Error(), + }, + } + } + + return da.ResultCheckBlock{ + DAResult: da.DAResult{ + Code: da.StatusSuccess, + DAHeight: dataLayerHeight, + }, + DataAvailable: len(shares) > 0, + } +} + +func (c *DataAvailabilityLayerClient) RetrieveBlocks(dataLayerHeight uint64) da.ResultRetrieveBlocks { + data, err := c.client.NamespacedData(context.TODO(), c.config.NamespaceID, dataLayerHeight) + if err != nil { + return da.ResultRetrieveBlocks{ + DAResult: da.DAResult{ + Code: da.StatusError, + Message: err.Error(), + }, + } + } + + blocks := make([]*types.Block, len(data)) + for i, msg := range data { + var block pb.Block + err = proto.Unmarshal(msg, &block) + if err != nil { + c.logger.Error("failed to unmarshal block", "daHeight", dataLayerHeight, "position", i, "error", err) + continue + } + blocks[i] = new(types.Block) + err := blocks[i].FromProto(&block) + if err != nil { + return da.ResultRetrieveBlocks{ + DAResult: da.DAResult{ + Code: da.StatusError, + Message: err.Error(), + }, + } + } + } + + return da.ResultRetrieveBlocks{ + DAResult: da.DAResult{ + Code: da.StatusSuccess, + DAHeight: dataLayerHeight, + }, + Blocks: blocks, + } +} diff --git a/da/celestia/mock/messages.go b/da/celestia/mock/messages.go new file mode 100644 index 0000000000..5864582761 --- /dev/null +++ b/da/celestia/mock/messages.go @@ -0,0 +1,106 @@ +package mock + +import ( + "bytes" + "encoding/binary" +) + +// This code is extracted from celestia-app. It's here to build shares from messages (serialized blocks). +// TODO(tzdybal): if we stop using `/namespaced_shares` we can get rid of this file. + +const ( + ShareSize = 256 + NamespaceSize = 8 + MsgShareSize = ShareSize - NamespaceSize +) + +// splitMessage breaks the data in a message into the minimum number of +// namespaced shares +func splitMessage(rawData []byte, nid []byte) []NamespacedShare { + shares := make([]NamespacedShare, 0) + firstRawShare := append(append( + make([]byte, 0, ShareSize), + nid...), + rawData[:MsgShareSize]..., + ) + shares = append(shares, NamespacedShare{firstRawShare, nid}) + rawData = rawData[MsgShareSize:] + for len(rawData) > 0 { + shareSizeOrLen := min(MsgShareSize, len(rawData)) + rawShare := append(append( + make([]byte, 0, ShareSize), + nid...), + rawData[:shareSizeOrLen]..., + ) + paddedShare := zeroPadIfNecessary(rawShare, ShareSize) + share := NamespacedShare{paddedShare, nid} + shares = append(shares, share) + rawData = rawData[shareSizeOrLen:] + } + return shares +} + +// Share contains the raw share data without the corresponding namespace. +type Share []byte + +// NamespacedShare extends a Share with the corresponding namespace. +type NamespacedShare struct { + Share + ID []byte +} + +func min(a, b int) int { + if a <= b { + return a + } + return b +} + +func zeroPadIfNecessary(share []byte, width int) []byte { + oldLen := len(share) + if oldLen < width { + missingBytes := width - oldLen + padByte := []byte{0} + padding := bytes.Repeat(padByte, missingBytes) + share = append(share, padding...) + return share + } + return share +} + +// marshalDelimited marshals the raw data (excluding the namespace) of this +// message and prefixes it with the length of that encoding. +func marshalDelimited(data []byte) ([]byte, error) { + lenBuf := make([]byte, binary.MaxVarintLen64) + length := uint64(len(data)) + n := binary.PutUvarint(lenBuf, length) + return append(lenBuf[:n], data...), nil +} + +// appendToShares appends raw data as shares. +// Used to build shares from blocks/messages. +func appendToShares(shares []NamespacedShare, nid []byte, rawData []byte) []NamespacedShare { + if len(rawData) <= MsgShareSize { + rawShare := append(append( + make([]byte, 0, len(nid)+len(rawData)), + nid...), + rawData..., + ) + paddedShare := zeroPadIfNecessary(rawShare, ShareSize) + share := NamespacedShare{paddedShare, nid} + shares = append(shares, share) + } else { // len(rawData) > MsgShareSize + shares = append(shares, splitMessage(rawData, nid)...) + } + return shares +} + +type namespacedSharesResponse struct { + Shares []Share `json:"shares"` + Height uint64 `json:"height"` +} + +type namespacedDataResponse struct { + Data [][]byte `json:"data"` + Height uint64 `json:"height"` +} diff --git a/da/celestia/mock/server.go b/da/celestia/mock/server.go new file mode 100644 index 0000000000..46a4643519 --- /dev/null +++ b/da/celestia/mock/server.go @@ -0,0 +1,213 @@ +package mock + +import ( + "context" + "encoding/hex" + "encoding/json" + "errors" + "net" + "net/http" + "strconv" + "time" + + mux2 "github.com/gorilla/mux" + + "github.com/celestiaorg/optimint/da" + mockda "github.com/celestiaorg/optimint/da/mock" + "github.com/celestiaorg/optimint/libs/cnrc" + "github.com/celestiaorg/optimint/log" + "github.com/celestiaorg/optimint/store" + "github.com/celestiaorg/optimint/types" +) + +type Server struct { + mock *mockda.MockDataAvailabilityLayerClient + blockTime time.Duration + server *http.Server + logger log.Logger +} + +func NewServer(blockTime time.Duration, logger log.Logger) *Server { + return &Server{ + mock: new(mockda.MockDataAvailabilityLayerClient), + blockTime: blockTime, + logger: logger, + } +} + +func (s *Server) Start(listener net.Listener) error { + err := s.mock.Init([]byte(s.blockTime.String()), store.NewDefaultInMemoryKVStore(), s.logger) + if err != nil { + return err + } + err = s.mock.Start() + if err != nil { + return err + } + go func() { + s.server = new(http.Server) + s.server.Handler = s.getHandler() + err := s.server.Serve(listener) + s.logger.Debug("http server exited with", "error", err) + }() + return nil +} + +func (s *Server) Stop() { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + _ = s.server.Shutdown(ctx) +} + +func (s *Server) getHandler() http.Handler { + mux := mux2.NewRouter() + mux.HandleFunc("/submit_pfd", s.submit).Methods(http.MethodPost) + mux.HandleFunc("/namespaced_shares/{namespace}/height/{height}", s.shares).Methods(http.MethodGet) + mux.HandleFunc("/namespaced_data/{namespace}/height/{height}", s.data).Methods(http.MethodGet) + + return mux +} + +func (s *Server) submit(w http.ResponseWriter, r *http.Request) { + req := cnrc.SubmitPFDRequest{} + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + s.writeError(w, err) + return + } + + block := types.Block{} + blockData, err := hex.DecodeString(req.Data) + if err != nil { + s.writeError(w, err) + return + } + err = block.UnmarshalBinary(blockData) + if err != nil { + s.writeError(w, err) + return + } + + res := s.mock.SubmitBlock(&block) + + resp, err := json.Marshal(cnrc.TxResponse{ + Height: int64(res.DAHeight), + Code: uint32(res.Code), + RawLog: res.Message, + }) + if err != nil { + s.writeError(w, err) + return + } + + s.writeResponse(w, resp) +} + +func (s *Server) shares(w http.ResponseWriter, r *http.Request) { + height, err := parseHeight(r) + if err != nil { + s.writeError(w, err) + return + } + + res := s.mock.RetrieveBlocks(height) + if res.Code != da.StatusSuccess { + s.writeError(w, errors.New(res.Message)) + return + } + + var nShares []NamespacedShare + for _, block := range res.Blocks { + blob, err := block.MarshalBinary() + if err != nil { + s.writeError(w, err) + return + } + delimited, err := marshalDelimited(blob) + if err != nil { + s.writeError(w, err) + } + nShares = appendToShares(nShares, []byte{1, 2, 3, 4, 5, 6, 7, 8}, delimited) + } + shares := make([]Share, len(nShares)) + for i := range nShares { + shares[i] = nShares[i].Share + } + + resp, err := json.Marshal(namespacedSharesResponse{ + Shares: shares, + Height: res.DAHeight, + }) + if err != nil { + s.writeError(w, err) + return + } + + s.writeResponse(w, resp) +} + +func (s *Server) data(w http.ResponseWriter, r *http.Request) { + height, err := parseHeight(r) + if err != nil { + s.writeError(w, err) + return + } + + res := s.mock.RetrieveBlocks(height) + if res.Code != da.StatusSuccess { + s.writeError(w, errors.New(res.Message)) + return + } + + data := make([][]byte, len(res.Blocks)) + for i := range res.Blocks { + data[i], err = res.Blocks[i].MarshalBinary() + if err != nil { + s.writeError(w, err) + return + } + } + + resp, err := json.Marshal(namespacedDataResponse{ + Data: data, + Height: res.DAHeight, + }) + if err != nil { + s.writeError(w, err) + return + } + + s.writeResponse(w, resp) +} + +func parseHeight(r *http.Request) (uint64, error) { + vars := mux2.Vars(r) + + height, err := strconv.ParseUint(vars["height"], 10, 64) + if err != nil { + return 0, err + } + return height, nil +} + +func (s *Server) writeResponse(w http.ResponseWriter, payload []byte) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err := w.Write(payload) + if err != nil { + s.logger.Error("failed to write response", "error", err) + } +} + +func (s *Server) writeError(w http.ResponseWriter, err error) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + resp, jerr := json.Marshal(err.Error()) + if jerr != nil { + s.logger.Error("failed to serialize error message", "error", jerr) + } + _, werr := w.Write(resp) + if werr != nil { + s.logger.Error("failed to write response", "error", werr) + } +} diff --git a/da/registry/registry.go b/da/registry/registry.go index 68a28b487e..6337fb524a 100644 --- a/da/registry/registry.go +++ b/da/registry/registry.go @@ -2,14 +2,16 @@ package registry import ( "github.com/celestiaorg/optimint/da" + "github.com/celestiaorg/optimint/da/celestia" "github.com/celestiaorg/optimint/da/grpc" "github.com/celestiaorg/optimint/da/mock" ) // this is a central registry for all Data Availability Layer Clients var clients = map[string]func() da.DataAvailabilityLayerClient{ - "mock": func() da.DataAvailabilityLayerClient { return &mock.MockDataAvailabilityLayerClient{} }, - "grpc": func() da.DataAvailabilityLayerClient { return &grpc.DataAvailabilityLayerClient{} }, + "mock": func() da.DataAvailabilityLayerClient { return &mock.MockDataAvailabilityLayerClient{} }, + "grpc": func() da.DataAvailabilityLayerClient { return &grpc.DataAvailabilityLayerClient{} }, + "celestia": func() da.DataAvailabilityLayerClient { return &celestia.DataAvailabilityLayerClient{} }, } // GetClient returns client identified by name. diff --git a/da/registry/registry_test.go b/da/registry/registry_test.go index a28e1bd8a5..0eb8d00cfe 100644 --- a/da/registry/registry_test.go +++ b/da/registry/registry_test.go @@ -9,7 +9,7 @@ import ( func TestRegistery(t *testing.T) { assert := assert.New(t) - expected := []string{"mock", "grpc"} + expected := []string{"mock", "grpc", "celestia"} actual := RegisteredClients() assert.ElementsMatch(expected, actual) diff --git a/da/test/da_test.go b/da/test/da_test.go index 460ab41e40..f5596f760b 100644 --- a/da/test/da_test.go +++ b/da/test/da_test.go @@ -1,6 +1,7 @@ package test import ( + "encoding/json" "math/rand" "net" "strconv" @@ -12,6 +13,8 @@ import ( "google.golang.org/grpc" "github.com/celestiaorg/optimint/da" + "github.com/celestiaorg/optimint/da/celestia" + cmock "github.com/celestiaorg/optimint/da/celestia/mock" grpcda "github.com/celestiaorg/optimint/da/grpc" "github.com/celestiaorg/optimint/da/grpc/mockserv" "github.com/celestiaorg/optimint/da/mock" @@ -24,8 +27,9 @@ import ( const mockDaBlockTime = 100 * time.Millisecond func TestLifecycle(t *testing.T) { - srv := startMockServ(t) + srv := startMockGRPCServ(t) defer srv.GracefulStop() + for _, dalc := range registry.RegisteredClients() { t.Run(dalc, func(t *testing.T) { doTestLifecycle(t, registry.GetClient(dalc)) @@ -47,8 +51,12 @@ func doTestLifecycle(t *testing.T, dalc da.DataAvailabilityLayerClient) { } func TestDALC(t *testing.T) { - srv := startMockServ(t) - defer srv.GracefulStop() + grpcServer := startMockGRPCServ(t) + defer grpcServer.GracefulStop() + + httpServer := startMockCelestiaNodeServer(t) + defer httpServer.Stop() + for _, dalc := range registry.RegisteredClients() { t.Run(dalc, func(t *testing.T) { doTestDALC(t, registry.GetClient(dalc)) @@ -65,6 +73,15 @@ func doTestDALC(t *testing.T, dalc da.DataAvailabilityLayerClient) { if _, ok := dalc.(*mock.MockDataAvailabilityLayerClient); ok { conf = []byte(mockDaBlockTime.String()) } + if _, ok := dalc.(*celestia.DataAvailabilityLayerClient); ok { + config := celestia.Config{ + BaseURL: "http://localhost:26658", + Timeout: 30 * time.Second, + GasLimit: 3000000, + NamespaceID: [8]byte{0, 1, 2, 3, 4, 5, 6, 7}, + } + conf, _ = json.Marshal(config) + } err := dalc.Init(conf, store.NewDefaultInMemoryKVStore(), test.NewTestLogger(t)) require.NoError(err) @@ -104,8 +121,12 @@ func doTestDALC(t *testing.T, dalc da.DataAvailabilityLayerClient) { } func TestRetrieve(t *testing.T) { - srv := startMockServ(t) - defer srv.GracefulStop() + grpcServer := startMockGRPCServ(t) + defer grpcServer.GracefulStop() + + httpServer := startMockCelestiaNodeServer(t) + defer httpServer.Stop() + for _, client := range registry.RegisteredClients() { t.Run(client, func(t *testing.T) { dalc := registry.GetClient(client) @@ -117,7 +138,8 @@ func TestRetrieve(t *testing.T) { } } -func startMockServ(t *testing.T) *grpc.Server { +func startMockGRPCServ(t *testing.T) *grpc.Server { + t.Helper() conf := grpcda.DefaultConfig srv := mockserv.GetServer(store.NewDefaultInMemoryKVStore(), conf, []byte(mockDaBlockTime.String())) lis, err := net.Listen("tcp", conf.Host+":"+strconv.Itoa(conf.Port)) @@ -130,6 +152,20 @@ func startMockServ(t *testing.T) *grpc.Server { return srv } +func startMockCelestiaNodeServer(t *testing.T) *cmock.Server { + t.Helper() + httpSrv := cmock.NewServer(mockDaBlockTime, test.NewTestLogger(t)) + l, err := net.Listen("tcp4", ":26658") + if err != nil { + t.Fatal("failed to create listener for mock celestia-node RPC server", "error", err) + } + err = httpSrv.Start(l) + if err != nil { + t.Fatal("can't start mock celestia-node RPC server") + } + return httpSrv +} + func doTestRetrieve(t *testing.T, dalc da.DataAvailabilityLayerClient) { require := require.New(t) assert := assert.New(t) @@ -139,6 +175,15 @@ func doTestRetrieve(t *testing.T, dalc da.DataAvailabilityLayerClient) { if _, ok := dalc.(*mock.MockDataAvailabilityLayerClient); ok { conf = []byte(mockDaBlockTime.String()) } + if _, ok := dalc.(*celestia.DataAvailabilityLayerClient); ok { + config := celestia.Config{ + BaseURL: "http://localhost:26658", + Timeout: 30 * time.Second, + GasLimit: 3000000, + NamespaceID: [8]byte{0, 1, 2, 3, 4, 5, 6, 7}, + } + conf, _ = json.Marshal(config) + } err := dalc.Init(conf, store.NewDefaultInMemoryKVStore(), test.NewTestLogger(t)) require.NoError(err) @@ -155,7 +200,7 @@ func doTestRetrieve(t *testing.T, dalc da.DataAvailabilityLayerClient) { for i := uint64(0); i < 100; i++ { b := getRandomBlock(i, rand.Int()%20) resp := dalc.SubmitBlock(b) - assert.Equal(da.StatusSuccess, resp.Code) + assert.Equal(da.StatusSuccess, resp.Code, resp.Message) time.Sleep(time.Duration(rand.Int63() % mockDaBlockTime.Milliseconds())) countAtHeight[resp.DAHeight]++ @@ -166,17 +211,18 @@ func doTestRetrieve(t *testing.T, dalc da.DataAvailabilityLayerClient) { time.Sleep(mockDaBlockTime + 20*time.Millisecond) for h, cnt := range countAtHeight { + t.Log("Retrieving block, DA Height", h) ret := retriever.RetrieveBlocks(h) - assert.Equal(da.StatusSuccess, ret.Code) - require.NotEmpty(ret.Blocks) - assert.Len(ret.Blocks, cnt) + assert.Equal(da.StatusSuccess, ret.Code, ret.Message) + require.NotEmpty(ret.Blocks, h) + assert.Len(ret.Blocks, cnt, h) } for b, h := range blocks { ret := retriever.RetrieveBlocks(h) - assert.Equal(da.StatusSuccess, ret.Code) - require.NotEmpty(ret.Blocks) - assert.Contains(ret.Blocks, b) + assert.Equal(da.StatusSuccess, ret.Code, h) + require.NotEmpty(ret.Blocks, h) + assert.Contains(ret.Blocks, b, h) } } diff --git a/go.mod b/go.mod index 7a30d8b7eb..e23cdce0d5 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.17 require ( github.com/dgraph-io/badger/v3 v3.2103.2 github.com/go-kit/kit v0.12.0 + github.com/go-resty/resty/v2 v2.7.0 github.com/gogo/protobuf v1.3.2 github.com/google/orderedcode v0.0.1 github.com/gorilla/rpc v1.2.0 @@ -15,6 +16,7 @@ require ( github.com/libp2p/go-libp2p-kad-dht v0.15.0 github.com/libp2p/go-libp2p-pubsub v0.6.1 github.com/multiformats/go-multiaddr v0.5.0 + github.com/ory/dockertest v3.3.5+incompatible github.com/prometheus/client_golang v1.12.2 github.com/rs/cors v1.8.2 github.com/spf13/cobra v1.4.0 @@ -27,15 +29,20 @@ require ( ) require ( + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/DataDog/zstd v1.4.5 // indirect + github.com/Microsoft/go-winio v0.5.1 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd v0.22.0-beta // indirect + github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/containerd/cgroups v1.0.3 // indirect + github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 // indirect github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -43,6 +50,7 @@ require ( github.com/dgraph-io/badger/v2 v2.2007.2 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect + github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect @@ -62,6 +70,7 @@ require ( github.com/google/gopacket v1.1.19 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f // indirect + github.com/gorilla/mux v1.8.0 // indirect github.com/gtank/merlin v0.1.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -143,6 +152,9 @@ require ( github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/gomega v1.16.0 // indirect + github.com/opencontainers/go-digest v1.0.0-rc1 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opencontainers/runc v1.0.2 // indirect github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect @@ -158,6 +170,7 @@ require ( github.com/raulk/clock v1.1.0 // indirect github.com/raulk/go-watchdog v1.2.0 // indirect github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect + github.com/sirupsen/logrus v1.8.1 // indirect github.com/smartystreets/assertions v1.0.1 // indirect github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect diff --git a/go.sum b/go.sum index 7a97252c6e..cc99cbaccf 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,7 @@ github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOv github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -78,6 +79,9 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -152,6 +156,7 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -180,6 +185,7 @@ github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTF github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -235,6 +241,7 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -296,6 +303,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= +github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= 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= @@ -416,6 +425,8 @@ github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f/go.mod h1:wJfORR github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -423,6 +434,7 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -1109,9 +1121,13 @@ github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je4 github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= @@ -1129,6 +1145,7 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -1264,6 +1281,7 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= @@ -1575,6 +1593,7 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -2011,6 +2030,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/libs/cnrc/client.go b/libs/cnrc/client.go new file mode 100644 index 0000000000..b4855678b0 --- /dev/null +++ b/libs/cnrc/client.go @@ -0,0 +1,121 @@ +package cnrc + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + c *resty.Client +} + +func NewClient(baseURL string, options ...Option) (*Client, error) { + c := &Client{ + c: resty.New(), + } + + c.c.SetBaseURL(baseURL) + + for _, option := range options { + if err := option(c); err != nil { + return nil, err + } + } + + return c, nil +} + +func (c *Client) Header(ctx context.Context, height uint64) /* Header */ error { + _ = headerPath() + return errors.New("method Header not implemented") +} + +func (c *Client) Balance(ctx context.Context) error { + _ = balanceEndpoint + return errors.New("method Balance not implemented") +} + +func (c *Client) SubmitTx(ctx context.Context, tx []byte) /* TxResponse */ error { + _ = submitTxEndpoint + return errors.New("method SubmitTx not implemented") +} + +func (c *Client) SubmitPFD(ctx context.Context, namespaceID [8]byte, data []byte, gasLimit uint64) (*TxResponse, error) { + req := SubmitPFDRequest{ + NamespaceID: hex.EncodeToString(namespaceID[:]), + Data: hex.EncodeToString(data), + GasLimit: gasLimit, + } + var res TxResponse + var rpcErr string + _, err := c.c.R(). + SetContext(ctx). + SetBody(req). + SetResult(&res). + SetError(&rpcErr). + Post(submitPFDEndpoint) + if err != nil { + return nil, err + } + if rpcErr != "" { + return nil, errors.New(rpcErr) + } + return &res, nil +} + +func (c *Client) NamespacedShares(ctx context.Context, namespaceID [8]byte, height uint64) ([][]byte, error) { + var res struct { + Shares [][]byte `json:"shares"` + Height uint64 `json:"height"` + } + + err := c.callNamespacedEndpoint(ctx, namespaceID, height, namespacedSharesEndpoint, &res) + if err != nil { + return nil, err + } + + return res.Shares, nil +} + +func (c *Client) NamespacedData(ctx context.Context, namespaceID [8]byte, height uint64) ([][]byte, error) { + var res struct { + Data [][]byte `json:"data"` + Height uint64 `json:"height"` + } + + err := c.callNamespacedEndpoint(ctx, namespaceID, height, namespacedDataEndpoint, &res) + if err != nil { + return nil, err + } + + return res.Data, nil +} + +// callNamespacedEndpoint fetches result of /namespaced_{type} family of endpoints into result (this should be pointer!) +func (c *Client) callNamespacedEndpoint(ctx context.Context, namespaceID [8]byte, height uint64, endpoint string, result interface{}) error { + var rpcErr string + _, err := c.c.R(). + SetContext(ctx). + SetResult(result). + SetError(&rpcErr). + Get(namespacedPath(endpoint, namespaceID, height)) + if err != nil { + return err + } + if rpcErr != "" { + return errors.New(rpcErr) + } + return nil +} + +func headerPath() string { + return fmt.Sprintf("%s/%s", headerEndpoint, heightKey) +} + +func namespacedPath(endpoint string, namespaceID [8]byte, height uint64) string { + return fmt.Sprintf("%s/%s/height/%d", endpoint, hex.EncodeToString(namespaceID[:]), height) +} diff --git a/libs/cnrc/client_test.go b/libs/cnrc/client_test.go new file mode 100644 index 0000000000..c18201792c --- /dev/null +++ b/libs/cnrc/client_test.go @@ -0,0 +1,55 @@ +package cnrc + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestNewClient(t *testing.T) { + cases := []struct { + name string + options []Option + expectedError error + }{ + {"without options", nil, nil}, + {"with timeout", []Option{WithTimeout(1 * time.Second)}, nil}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + client, err := NewClient("", c.options...) + assert.ErrorIs(t, err, c.expectedError) + if c.expectedError != nil { + assert.Nil(t, client) + } else { + assert.NotNil(t, client) + } + }) + } +} + +func TestNamespacedShares(t *testing.T) { + t.Skip() + client, err := NewClient("http://localhost:26658") + assert.NoError(t, err) + assert.NotNil(t, client) + + shares, err := client.NamespacedShares(context.TODO(), [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, 8) + assert.NoError(t, err) + assert.NotNil(t, shares) + assert.Len(t, shares, 4) +} + +func TestSubmitPDF(t *testing.T) { + t.Skip() + client, err := NewClient("http://localhost:26658", WithTimeout(30*time.Second)) + assert.NoError(t, err) + assert.NotNil(t, client) + + txRes, err := client.SubmitPFD(context.TODO(), [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, []byte("random data"), 100000) + assert.NoError(t, err) + assert.NotNil(t, txRes) +} diff --git a/libs/cnrc/consts.go b/libs/cnrc/consts.go new file mode 100644 index 0000000000..39c74adcb4 --- /dev/null +++ b/libs/cnrc/consts.go @@ -0,0 +1,12 @@ +package cnrc + +const ( + headerEndpoint = "/header" + balanceEndpoint = "/balance" + submitTxEndpoint = "/submit_tx" + submitPFDEndpoint = "/submit_pfd" + namespacedSharesEndpoint = "/namespaced_shares" + namespacedDataEndpoint = "/namespaced_data" + + heightKey = "height" +) diff --git a/libs/cnrc/doc.go b/libs/cnrc/doc.go new file mode 100644 index 0000000000..01388d3945 --- /dev/null +++ b/libs/cnrc/doc.go @@ -0,0 +1,2 @@ +// Package cnrc implements a Celestia Node RPC Client +package cnrc diff --git a/libs/cnrc/options.go b/libs/cnrc/options.go new file mode 100644 index 0000000000..477c446740 --- /dev/null +++ b/libs/cnrc/options.go @@ -0,0 +1,12 @@ +package cnrc + +import "time" + +type Option func(*Client) error + +func WithTimeout(timeout time.Duration) Option { + return func(c *Client) error { + c.c.SetTimeout(timeout) + return nil + } +} diff --git a/libs/cnrc/types.go b/libs/cnrc/types.go new file mode 100644 index 0000000000..08a719da7f --- /dev/null +++ b/libs/cnrc/types.go @@ -0,0 +1,78 @@ +package cnrc + +import "github.com/gogo/protobuf/types" + +// SubmitPFDRequest represents a request to submit a PayForData transaction. +type SubmitPFDRequest struct { + NamespaceID string `json:"namespace_id"` + Data string `json:"data"` + GasLimit uint64 `json:"gas_limit"` +} + +// Types below are copied from celestia-node (or cosmos-sdk dependency of celestia node, to be precise) +// They are needed for proper deserialization. +// It's probably far from the best approach to those types, but it's simple and works. +// Some alternatives: +// 1. Generate types from protobuf definitions (and automate updating of protobuf files) +// 2. Extract common dependency that defines all types used in RPC. + +// TxResponse defines a structure containing relevant tx data and metadata. The +// tags are stringified and the log is JSON decoded. +type TxResponse struct { + // The block height + Height int64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + // The transaction hash. + TxHash string `protobuf:"bytes,2,opt,name=txhash,proto3" json:"txhash,omitempty"` + // Namespace for the Code + Codespace string `protobuf:"bytes,3,opt,name=codespace,proto3" json:"codespace,omitempty"` + // Response code. + Code uint32 `protobuf:"varint,4,opt,name=code,proto3" json:"code,omitempty"` + // Result bytes, if any. + Data string `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` + // The output of the application's logger (raw string). May be + // non-deterministic. + RawLog string `protobuf:"bytes,6,opt,name=raw_log,json=rawLog,proto3" json:"raw_log,omitempty"` + // The output of the application's logger (typed). May be non-deterministic. + Logs ABCIMessageLogs `protobuf:"bytes,7,rep,name=logs,proto3,castrepeated=ABCIMessageLogs" json:"logs"` + // Additional information. May be non-deterministic. + Info string `protobuf:"bytes,8,opt,name=info,proto3" json:"info,omitempty"` + // Amount of gas requested for transaction. + GasWanted int64 `protobuf:"varint,9,opt,name=gas_wanted,json=gasWanted,proto3" json:"gas_wanted,omitempty"` + // Amount of gas consumed by transaction. + GasUsed int64 `protobuf:"varint,10,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"` + // The request transaction bytes. + Tx *types.Any `protobuf:"bytes,11,opt,name=tx,proto3" json:"tx,omitempty"` + // Time of the previous block. For heights > 1, it's the weighted median of + // the timestamps of the valid votes in the block.LastCommit. For height == 1, + // it's genesis time. + Timestamp string `protobuf:"bytes,12,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +// ABCIMessageLogs represents a slice of ABCIMessageLog. +type ABCIMessageLogs []ABCIMessageLog + +// ABCIMessageLog defines a structure containing an indexed tx ABCI message log. +type ABCIMessageLog struct { + MsgIndex uint32 `protobuf:"varint,1,opt,name=msg_index,json=msgIndex,proto3" json:"msg_index,omitempty"` + Log string `protobuf:"bytes,2,opt,name=log,proto3" json:"log,omitempty"` + // Events contains a slice of Event objects that were emitted during some + // execution. + Events StringEvents `protobuf:"bytes,3,rep,name=events,proto3,castrepeated=StringEvents" json:"events"` +} + +// StringAttributes defines a slice of StringEvents objects. +type StringEvents []StringEvent + +// StringEvent defines en Event object wrapper where all the attributes +// contain key/value pairs that are strings instead of raw bytes. +type StringEvent struct { + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Attributes []Attribute `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes"` +} + +// Attribute defines an attribute wrapper where the key and value are +// strings instead of raw bytes. +type Attribute struct { + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +}