From 6fad566f68e6af6718b32f460f1cd4a896ce55f3 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Mon, 27 May 2024 14:44:33 +0200 Subject: [PATCH 01/40] composition redesign --- share/shwap/p2p/bitswap/bitswap.go | 112 +++ share/shwap/p2p/bitswap/bitswap_test.go | 108 +++ share/shwap/p2p/bitswap/hasher.go | 62 ++ share/shwap/p2p/bitswap/hasher_test.go | 1 + share/shwap/p2p/bitswap/id_registry.go | 78 ++ share/shwap/p2p/bitswap/pb/bitswap.pb.go | 852 ++++++++++++++++++ share/shwap/p2p/bitswap/pb/bitswap.proto | 20 + share/shwap/p2p/bitswap/row.go | 107 +++ share/shwap/p2p/bitswap/row_namespace_data.go | 112 +++ .../p2p/bitswap/row_namespace_data_test.go | 41 + share/shwap/p2p/bitswap/row_test.go | 48 + share/shwap/p2p/bitswap/sample.go | 112 +++ share/shwap/p2p/bitswap/sample_test.go | 43 + 13 files changed, 1696 insertions(+) create mode 100644 share/shwap/p2p/bitswap/bitswap.go create mode 100644 share/shwap/p2p/bitswap/bitswap_test.go create mode 100644 share/shwap/p2p/bitswap/hasher.go create mode 100644 share/shwap/p2p/bitswap/hasher_test.go create mode 100644 share/shwap/p2p/bitswap/id_registry.go create mode 100644 share/shwap/p2p/bitswap/pb/bitswap.pb.go create mode 100644 share/shwap/p2p/bitswap/pb/bitswap.proto create mode 100644 share/shwap/p2p/bitswap/row.go create mode 100644 share/shwap/p2p/bitswap/row_namespace_data.go create mode 100644 share/shwap/p2p/bitswap/row_namespace_data_test.go create mode 100644 share/shwap/p2p/bitswap/row_test.go create mode 100644 share/shwap/p2p/bitswap/sample.go create mode 100644 share/shwap/p2p/bitswap/sample_test.go diff --git a/share/shwap/p2p/bitswap/bitswap.go b/share/shwap/p2p/bitswap/bitswap.go new file mode 100644 index 0000000000..6ffa2cb8ab --- /dev/null +++ b/share/shwap/p2p/bitswap/bitswap.go @@ -0,0 +1,112 @@ +package bitswap + +import ( + "context" + "fmt" + "hash" + "sync" + + "github.com/ipfs/boxo/exchange" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + logger "github.com/ipfs/go-log/v2" + mh "github.com/multiformats/go-multihash" + + "github.com/celestiaorg/celestia-node/share" +) + +var log = logger.Logger("shwap/bitswap") + +// TODO: +// * Synchronization for GetContainers +// * Test with race and count 100 +// * Hasher test +// * Coverage +// * godoc +// * document steps required to add new id/container type + +type ID[C any] interface { + String() string + CID() cid.Cid + UnmarshalContainer(*share.Root, []byte) (C, error) +} + +func RegisterID(mhcode, codec uint64, size int, bldrFn func(cid2 cid.Cid) (blockBuilder, error)) { + mh.Register(mhcode, func() hash.Hash { + return &hasher{IDSize: size} + }) + specRegistry[mhcode] = idSpec{ + size: size, + codec: codec, + builder: bldrFn, + } +} + +// GetContainers +// Does not guarantee synchronization. Calling this func simultaneously with the same ID may cause +// issues. TODO: Describe motivation +func GetContainers[C any](ctx context.Context, fetcher exchange.Fetcher, root *share.Root, ids ...ID[C]) ([]C, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + cids := make([]cid.Cid, len(ids)) + cntrs := make([]C, len(ids)) + for i, id := range ids { + i := i + cids[i] = id.CID() + + idStr := id.String() + globalVerifiers.add(idStr, func(data []byte) error { + cntr, err := id.UnmarshalContainer(root, data) + if err != nil { + return err + } + + cntrs[i] = cntr + return nil + }) + defer globalVerifiers.release(idStr) + } + + // must start getting only after verifiers are registered + blkCh, err := fetcher.GetBlocks(ctx, cids) + if err != nil { + return nil, fmt.Errorf("fetching bitswap blocks: %w", err) + } + + // GetBlocks handles ctx and closes blkCh, so we don't have to + blks := make([]blocks.Block, 0, len(cids)) + for blk := range blkCh { + blks = append(blks, blk) + } + if len(blks) != len(cids) { + if ctx.Err() != nil { + return nil, ctx.Err() + } + return nil, fmt.Errorf("not all the containers were found") + } + + return cntrs, nil +} + +var globalVerifiers verifiers + +type verifiers struct { + sync.Map +} + +func (vs *verifiers) add(key string, v func([]byte) error) { + vs.Store(key, v) +} + +func (vs *verifiers) get(key string) func([]byte) error { + v, ok := vs.Load(key) + if !ok { + return nil + } + return v.(func([]byte) error) +} + +func (vs *verifiers) release(key string) { + vs.Delete(key) +} diff --git a/share/shwap/p2p/bitswap/bitswap_test.go b/share/shwap/p2p/bitswap/bitswap_test.go new file mode 100644 index 0000000000..631c29d19c --- /dev/null +++ b/share/shwap/p2p/bitswap/bitswap_test.go @@ -0,0 +1,108 @@ +package bitswap + +import ( + "context" + "fmt" + "testing" + + "github.com/ipfs/boxo/bitswap" + "github.com/ipfs/boxo/bitswap/network" + "github.com/ipfs/boxo/blockstore" + "github.com/ipfs/boxo/exchange" + "github.com/ipfs/boxo/routing/offline" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ds "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + record "github.com/libp2p/go-libp2p-record" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/rsmt2d" +) + +func remoteClient(ctx context.Context, t *testing.T, bstore blockstore.Blockstore) exchange.Fetcher { + net, err := mocknet.FullMeshLinked(2) + require.NoError(t, err) + + dstore := dssync.MutexWrap(ds.NewMapDatastore()) + routing := offline.NewOfflineRouter(dstore, record.NamespacedValidator{}) + _ = bitswap.New( + ctx, + network.NewFromIpfsHost(net.Hosts()[0], routing), + bstore, + ) + + dstoreClient := dssync.MutexWrap(ds.NewMapDatastore()) + bstoreClient := blockstore.NewBlockstore(dstoreClient) + routingClient := offline.NewOfflineRouter(dstoreClient, record.NamespacedValidator{}) + + bitswapClient := bitswap.New( + ctx, + network.NewFromIpfsHost(net.Hosts()[1], routingClient), + bstoreClient, + ) + + err = net.ConnectAllButSelf() + require.NoError(t, err) + + return bitswapClient +} + +type testBlockstore struct { + eds *rsmt2d.ExtendedDataSquare +} + +func newTestBlockstore(eds *rsmt2d.ExtendedDataSquare) *testBlockstore { + return &testBlockstore{eds: eds} +} + +func (b *testBlockstore) Get(_ context.Context, cid cid.Cid) (blocks.Block, error) { + spec, ok := specRegistry[cid.Prefix().MhType] + if !ok { + return nil, fmt.Errorf("unsupported codec") + } + + bldr, err := spec.builder(cid) + if err != nil { + return nil, err + } + + return bldr.BlockFromEDS(b.eds) +} + +func (b *testBlockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { + blk, err := b.Get(ctx, cid) + if err != nil { + return 0, err + } + return len(blk.RawData()), nil +} + +func (b *testBlockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { + _, err := b.Get(ctx, cid) + if err != nil { + return false, err + } + return true, nil +} + +func (b *testBlockstore) Put(context.Context, blocks.Block) error { + panic("not implemented") +} + +func (b *testBlockstore) PutMany(context.Context, []blocks.Block) error { + panic("not implemented") +} + +func (b *testBlockstore) DeleteBlock(context.Context, cid.Cid) error { + panic("not implemented") +} + +func (b *testBlockstore) AllKeysChan(context.Context) (<-chan cid.Cid, error) { + panic("not implemented") +} + +func (b *testBlockstore) HashOnRead(bool) { + panic("not implemented") +} diff --git a/share/shwap/p2p/bitswap/hasher.go b/share/shwap/p2p/bitswap/hasher.go new file mode 100644 index 0000000000..8861a77ad9 --- /dev/null +++ b/share/shwap/p2p/bitswap/hasher.go @@ -0,0 +1,62 @@ +package bitswap + +import ( + "crypto/sha256" + "fmt" +) + +// hasher +// TODO: Describe the Hack this all is +type hasher struct { + IDSize int + + sum []byte +} + +func (h *hasher) Write(data []byte) (int, error) { + const pbOffset = 2 // this assumes the protobuf serialization is in use + if len(data) < h.IDSize+pbOffset { + err := fmt.Errorf("shwap/bitswap hasher: insufficient data size") + log.Error() + return 0, err + } + // extract ID out of data + // we do this on the raw data to: + // * Avoid complicating hasher with generalized bytes -> type unmarshalling + // * Avoid type allocations + id := data[pbOffset : h.IDSize+pbOffset] + // get registered verifier and use it to check data validity + ver := globalVerifiers.get(string(id)) + if ver == nil { + err := fmt.Errorf("shwap/bitswap hasher: no verifier registered") + log.Error(err) + return 0, err + } + err := ver(data) + if err != nil { + err = fmt.Errorf("shwap/bitswap hasher: verifying data: %w", err) + log.Error(err) + return 0, err + } + // if correct set the id as resulting sum + // it's required for the sum to match the original ID + // to satisfy hash contract + h.sum = id + return len(data), err +} + +func (h *hasher) Sum([]byte) []byte { + return h.sum +} + +func (h *hasher) Reset() { + h.sum = nil +} + +func (h *hasher) Size() int { + return h.IDSize +} + +func (h *hasher) BlockSize() int { + return sha256.BlockSize +} diff --git a/share/shwap/p2p/bitswap/hasher_test.go b/share/shwap/p2p/bitswap/hasher_test.go new file mode 100644 index 0000000000..67a03afb70 --- /dev/null +++ b/share/shwap/p2p/bitswap/hasher_test.go @@ -0,0 +1 @@ +package bitswap diff --git a/share/shwap/p2p/bitswap/id_registry.go b/share/shwap/p2p/bitswap/id_registry.go new file mode 100644 index 0000000000..6d9ced3c69 --- /dev/null +++ b/share/shwap/p2p/bitswap/id_registry.go @@ -0,0 +1,78 @@ +package bitswap + +import ( + "encoding" + "fmt" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" + + "github.com/celestiaorg/rsmt2d" +) + +var specRegistry = make(map[uint64]idSpec) + +type idSpec struct { + size int + codec uint64 + builder func(cid.Cid) (blockBuilder, error) +} + +type blockBuilder interface { + BlockFromEDS(*rsmt2d.ExtendedDataSquare) (blocks.Block, error) +} + +// DefaultAllowlist keeps default list of multihashes allowed in the network. +// TODO(@Wondertan): Make it private and instead provide Blockservice constructor with injected +// allowlist +var DefaultAllowlist allowlist + +type allowlist struct{} + +func (a allowlist) IsAllowed(code uint64) bool { + // we disable all codes except registered + _, ok := specRegistry[code] + return ok +} + +func extractCID(cid cid.Cid) ([]byte, error) { + if err := validateCID(cid); err != nil { + return nil, err + } + // mhPrefixSize is the size of the multihash prefix that used to cut it off. + const mhPrefixSize = 4 + return cid.Hash()[mhPrefixSize:], nil +} + +func encodeCID(bm encoding.BinaryMarshaler, mhcode, codec uint64) cid.Cid { + data, err := bm.MarshalBinary() + if err != nil { + panic(fmt.Errorf("marshaling for CID: %w", err)) + } + + buf, err := mh.Encode(data, mhcode) + if err != nil { + panic(fmt.Errorf("encoding to CID: %w", err)) + } + + return cid.NewCidV1(codec, buf) +} + +func validateCID(cid cid.Cid) error { + prefix := cid.Prefix() + spec, ok := specRegistry[prefix.MhType] + if !ok { + return fmt.Errorf("unsupported multihash type %d", prefix.MhType) + } + + if prefix.Codec != spec.codec { + return fmt.Errorf("invalid CID codec %d", prefix.Codec) + } + + if prefix.MhLength != spec.size { + return fmt.Errorf("invalid multihash length %d", prefix.MhLength) + } + + return nil +} diff --git a/share/shwap/p2p/bitswap/pb/bitswap.pb.go b/share/shwap/p2p/bitswap/pb/bitswap.pb.go new file mode 100644 index 0000000000..b7ddf0f633 --- /dev/null +++ b/share/shwap/p2p/bitswap/pb/bitswap.pb.go @@ -0,0 +1,852 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: share/shwap/p2p/bitswap/pb/bitswap.proto + +package pb + +import ( + fmt "fmt" + pb "github.com/celestiaorg/celestia-node/share/shwap/pb" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type RowBlock struct { + RowId []byte `protobuf:"bytes,1,opt,name=row_id,json=rowId,proto3" json:"row_id,omitempty"` + Row *pb.Row `protobuf:"bytes,2,opt,name=row,proto3" json:"row,omitempty"` +} + +func (m *RowBlock) Reset() { *m = RowBlock{} } +func (m *RowBlock) String() string { return proto.CompactTextString(m) } +func (*RowBlock) ProtoMessage() {} +func (*RowBlock) Descriptor() ([]byte, []int) { + return fileDescriptor_09fd4e2ff1d5ce94, []int{0} +} +func (m *RowBlock) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RowBlock) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RowBlock.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RowBlock) XXX_Merge(src proto.Message) { + xxx_messageInfo_RowBlock.Merge(m, src) +} +func (m *RowBlock) XXX_Size() int { + return m.Size() +} +func (m *RowBlock) XXX_DiscardUnknown() { + xxx_messageInfo_RowBlock.DiscardUnknown(m) +} + +var xxx_messageInfo_RowBlock proto.InternalMessageInfo + +func (m *RowBlock) GetRowId() []byte { + if m != nil { + return m.RowId + } + return nil +} + +func (m *RowBlock) GetRow() *pb.Row { + if m != nil { + return m.Row + } + return nil +} + +type SampleBlock struct { + SampleId []byte `protobuf:"bytes,1,opt,name=sample_id,json=sampleId,proto3" json:"sample_id,omitempty"` + Sample *pb.Sample `protobuf:"bytes,2,opt,name=sample,proto3" json:"sample,omitempty"` +} + +func (m *SampleBlock) Reset() { *m = SampleBlock{} } +func (m *SampleBlock) String() string { return proto.CompactTextString(m) } +func (*SampleBlock) ProtoMessage() {} +func (*SampleBlock) Descriptor() ([]byte, []int) { + return fileDescriptor_09fd4e2ff1d5ce94, []int{1} +} +func (m *SampleBlock) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SampleBlock) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SampleBlock.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *SampleBlock) XXX_Merge(src proto.Message) { + xxx_messageInfo_SampleBlock.Merge(m, src) +} +func (m *SampleBlock) XXX_Size() int { + return m.Size() +} +func (m *SampleBlock) XXX_DiscardUnknown() { + xxx_messageInfo_SampleBlock.DiscardUnknown(m) +} + +var xxx_messageInfo_SampleBlock proto.InternalMessageInfo + +func (m *SampleBlock) GetSampleId() []byte { + if m != nil { + return m.SampleId + } + return nil +} + +func (m *SampleBlock) GetSample() *pb.Sample { + if m != nil { + return m.Sample + } + return nil +} + +type RowNamespaceDataBlock struct { + RowNamespaceDataId []byte `protobuf:"bytes,1,opt,name=row_namespace_data_id,json=rowNamespaceDataId,proto3" json:"row_namespace_data_id,omitempty"` + Data *pb.RowNamespaceData `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *RowNamespaceDataBlock) Reset() { *m = RowNamespaceDataBlock{} } +func (m *RowNamespaceDataBlock) String() string { return proto.CompactTextString(m) } +func (*RowNamespaceDataBlock) ProtoMessage() {} +func (*RowNamespaceDataBlock) Descriptor() ([]byte, []int) { + return fileDescriptor_09fd4e2ff1d5ce94, []int{2} +} +func (m *RowNamespaceDataBlock) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RowNamespaceDataBlock) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RowNamespaceDataBlock.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RowNamespaceDataBlock) XXX_Merge(src proto.Message) { + xxx_messageInfo_RowNamespaceDataBlock.Merge(m, src) +} +func (m *RowNamespaceDataBlock) XXX_Size() int { + return m.Size() +} +func (m *RowNamespaceDataBlock) XXX_DiscardUnknown() { + xxx_messageInfo_RowNamespaceDataBlock.DiscardUnknown(m) +} + +var xxx_messageInfo_RowNamespaceDataBlock proto.InternalMessageInfo + +func (m *RowNamespaceDataBlock) GetRowNamespaceDataId() []byte { + if m != nil { + return m.RowNamespaceDataId + } + return nil +} + +func (m *RowNamespaceDataBlock) GetData() *pb.RowNamespaceData { + if m != nil { + return m.Data + } + return nil +} + +func init() { + proto.RegisterType((*RowBlock)(nil), "bitswap.RowBlock") + proto.RegisterType((*SampleBlock)(nil), "bitswap.SampleBlock") + proto.RegisterType((*RowNamespaceDataBlock)(nil), "bitswap.RowNamespaceDataBlock") +} + +func init() { + proto.RegisterFile("share/shwap/p2p/bitswap/pb/bitswap.proto", fileDescriptor_09fd4e2ff1d5ce94) +} + +var fileDescriptor_09fd4e2ff1d5ce94 = []byte{ + // 295 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xcf, 0x4a, 0x03, 0x31, + 0x10, 0xc6, 0x1b, 0xff, 0xd4, 0x9a, 0xea, 0x25, 0x50, 0x2c, 0x55, 0x42, 0x29, 0x08, 0x05, 0xb1, + 0x8b, 0xf5, 0x01, 0x0a, 0xc5, 0xcb, 0x5e, 0x04, 0xe3, 0x49, 0x2f, 0x25, 0xbb, 0x09, 0xdd, 0xc5, + 0xdd, 0x4e, 0x48, 0x22, 0x79, 0x0d, 0x1f, 0xcb, 0x63, 0x8f, 0x1e, 0x65, 0xf7, 0x45, 0x64, 0x37, + 0x5b, 0x97, 0x0a, 0xde, 0xbe, 0xcc, 0x7c, 0xdf, 0x6f, 0x86, 0x09, 0x9e, 0x9a, 0x84, 0x6b, 0x19, + 0x98, 0xc4, 0x71, 0x15, 0xa8, 0xb9, 0x0a, 0xa2, 0xd4, 0x9a, 0x5a, 0x47, 0x3b, 0x39, 0x53, 0x1a, + 0x2c, 0x90, 0x93, 0xe6, 0x39, 0x1a, 0xed, 0x45, 0x22, 0x2f, 0xbc, 0x69, 0xb2, 0xc0, 0x3d, 0x06, + 0x6e, 0x99, 0x41, 0xfc, 0x46, 0x06, 0xb8, 0xab, 0xc1, 0xad, 0x52, 0x31, 0x44, 0x63, 0x34, 0x3d, + 0x63, 0xc7, 0x1a, 0x5c, 0x28, 0xc8, 0x15, 0x3e, 0xd4, 0xe0, 0x86, 0x07, 0x63, 0x34, 0xed, 0xcf, + 0xf1, 0xcc, 0xa7, 0x19, 0x38, 0x56, 0x95, 0x27, 0x4f, 0xb8, 0xff, 0xcc, 0x73, 0x95, 0x49, 0xcf, + 0xb8, 0xc4, 0xa7, 0xa6, 0x7e, 0xb6, 0x98, 0x9e, 0x2f, 0x84, 0x82, 0x5c, 0xe3, 0xae, 0xd7, 0x0d, + 0xec, 0xbc, 0x81, 0x79, 0x00, 0x6b, 0x9a, 0x13, 0x87, 0x07, 0x0c, 0xdc, 0x23, 0xcf, 0xa5, 0x51, + 0x3c, 0x96, 0x0f, 0xdc, 0x72, 0x0f, 0xbf, 0xc3, 0x83, 0x6a, 0xc1, 0xcd, 0xae, 0xb3, 0x12, 0xdc, + 0xf2, 0x76, 0x10, 0xd1, 0x7f, 0x52, 0xa1, 0x20, 0x37, 0xf8, 0xa8, 0x32, 0x35, 0x03, 0x2f, 0xda, + 0xed, 0xf7, 0x8c, 0xac, 0x36, 0x2d, 0x5f, 0x3e, 0x0b, 0x8a, 0xb6, 0x05, 0x45, 0xdf, 0x05, 0x45, + 0x1f, 0x25, 0xed, 0x6c, 0x4b, 0xda, 0xf9, 0x2a, 0x69, 0xe7, 0x75, 0xb1, 0x4e, 0x6d, 0xf2, 0x1e, + 0xcd, 0x62, 0xc8, 0x83, 0x58, 0x66, 0xd2, 0xd8, 0x94, 0x83, 0x5e, 0xff, 0xea, 0xdb, 0x0d, 0x88, + 0xea, 0xc2, 0xff, 0x7d, 0x4d, 0xd4, 0xad, 0xcf, 0x7d, 0xff, 0x13, 0x00, 0x00, 0xff, 0xff, 0xbe, + 0x9c, 0xa6, 0x0a, 0xbf, 0x01, 0x00, 0x00, +} + +func (m *RowBlock) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RowBlock) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RowBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Row != nil { + { + size, err := m.Row.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBitswap(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.RowId) > 0 { + i -= len(m.RowId) + copy(dAtA[i:], m.RowId) + i = encodeVarintBitswap(dAtA, i, uint64(len(m.RowId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *SampleBlock) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SampleBlock) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SampleBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sample != nil { + { + size, err := m.Sample.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBitswap(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.SampleId) > 0 { + i -= len(m.SampleId) + copy(dAtA[i:], m.SampleId) + i = encodeVarintBitswap(dAtA, i, uint64(len(m.SampleId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *RowNamespaceDataBlock) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RowNamespaceDataBlock) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RowNamespaceDataBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Data != nil { + { + size, err := m.Data.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBitswap(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.RowNamespaceDataId) > 0 { + i -= len(m.RowNamespaceDataId) + copy(dAtA[i:], m.RowNamespaceDataId) + i = encodeVarintBitswap(dAtA, i, uint64(len(m.RowNamespaceDataId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintBitswap(dAtA []byte, offset int, v uint64) int { + offset -= sovBitswap(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *RowBlock) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.RowId) + if l > 0 { + n += 1 + l + sovBitswap(uint64(l)) + } + if m.Row != nil { + l = m.Row.Size() + n += 1 + l + sovBitswap(uint64(l)) + } + return n +} + +func (m *SampleBlock) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SampleId) + if l > 0 { + n += 1 + l + sovBitswap(uint64(l)) + } + if m.Sample != nil { + l = m.Sample.Size() + n += 1 + l + sovBitswap(uint64(l)) + } + return n +} + +func (m *RowNamespaceDataBlock) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.RowNamespaceDataId) + if l > 0 { + n += 1 + l + sovBitswap(uint64(l)) + } + if m.Data != nil { + l = m.Data.Size() + n += 1 + l + sovBitswap(uint64(l)) + } + return n +} + +func sovBitswap(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozBitswap(x uint64) (n int) { + return sovBitswap(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RowBlock) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBitswap + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RowBlock: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RowBlock: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RowId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBitswap + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBitswap + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBitswap + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RowId = append(m.RowId[:0], dAtA[iNdEx:postIndex]...) + if m.RowId == nil { + m.RowId = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Row", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBitswap + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBitswap + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBitswap + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Row == nil { + m.Row = &pb.Row{} + } + if err := m.Row.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBitswap(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBitswap + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SampleBlock) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBitswap + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SampleBlock: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SampleBlock: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SampleId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBitswap + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBitswap + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBitswap + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SampleId = append(m.SampleId[:0], dAtA[iNdEx:postIndex]...) + if m.SampleId == nil { + m.SampleId = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sample", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBitswap + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBitswap + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBitswap + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Sample == nil { + m.Sample = &pb.Sample{} + } + if err := m.Sample.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBitswap(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBitswap + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RowNamespaceDataBlock) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBitswap + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RowNamespaceDataBlock: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RowNamespaceDataBlock: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RowNamespaceDataId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBitswap + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBitswap + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBitswap + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RowNamespaceDataId = append(m.RowNamespaceDataId[:0], dAtA[iNdEx:postIndex]...) + if m.RowNamespaceDataId == nil { + m.RowNamespaceDataId = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBitswap + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBitswap + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBitswap + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Data == nil { + m.Data = &pb.RowNamespaceData{} + } + if err := m.Data.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBitswap(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBitswap + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipBitswap(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBitswap + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBitswap + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBitswap + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthBitswap + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupBitswap + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthBitswap + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthBitswap = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowBitswap = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupBitswap = fmt.Errorf("proto: unexpected end of group") +) diff --git a/share/shwap/p2p/bitswap/pb/bitswap.proto b/share/shwap/p2p/bitswap/pb/bitswap.proto new file mode 100644 index 0000000000..ffa922fd02 --- /dev/null +++ b/share/shwap/p2p/bitswap/pb/bitswap.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package bitswap; +option go_package = "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb"; + +import "share/shwap/pb/shwap.proto"; + +message RowBlock { + bytes row_id = 1; + shwap.Row row = 2; +} + +message SampleBlock { + bytes sample_id = 1; + shwap.Sample sample = 2; +} + +message RowNamespaceDataBlock { + bytes row_namespace_data_id = 1; + shwap.RowNamespaceData data = 2; +} diff --git a/share/shwap/p2p/bitswap/row.go b/share/shwap/p2p/bitswap/row.go new file mode 100644 index 0000000000..2cc58dc2b8 --- /dev/null +++ b/share/shwap/p2p/bitswap/row.go @@ -0,0 +1,107 @@ +package bitswap + +import ( + "fmt" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/shwap" + bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" +) + +const ( + // rowCodec is a CID codec used for row Bitswap requests over Namespaced Merkle Tree. + rowCodec = 0x7800 + + // rowMultihashCode is the multihash code for custom axis sampling multihash function. + rowMultihashCode = 0x7801 +) + +func init() { + RegisterID( + rowMultihashCode, + rowCodec, + shwap.RowIDSize, + func(cid cid.Cid) (blockBuilder, error) { + return RowIDFromCID(cid) + }, + ) +} + +type RowID shwap.RowID + +// RowIDFromCID coverts CID to RowID. +func RowIDFromCID(cid cid.Cid) (RowID, error) { + ridData, err := extractCID(cid) + if err != nil { + return RowID{}, err + } + + rid, err := shwap.RowIDFromBinary(ridData) + if err != nil { + return RowID{}, fmt.Errorf("while unmarhaling RowID: %w", err) + } + return RowID(rid), nil +} + +func (rid RowID) String() string { + data, err := rid.MarshalBinary() + if err != nil { + panic(fmt.Errorf("marshaling RowID: %w", err)) + } + return string(data) +} + +func (rid RowID) MarshalBinary() ([]byte, error) { + return shwap.RowID(rid).MarshalBinary() +} + +func (rid RowID) CID() cid.Cid { + return encodeCID(rid, rowMultihashCode, rowCodec) +} + +func (rid RowID) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { + row := shwap.RowFromEDS(eds, rid.RowIndex, shwap.Left) + + dataID, err := rid.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("marshaling RowID: %w", err) + } + + rowBlk := bitswappb.RowBlock{ + RowId: dataID, + Row: row.ToProto(), + } + + dataBlk, err := rowBlk.Marshal() + if err != nil { + return nil, fmt.Errorf("marshaling RowBlock: %w", err) + } + + blk, err := blocks.NewBlockWithCid(dataBlk, rid.CID()) + if err != nil { + return nil, fmt.Errorf("assembling block: %w", err) + } + + return blk, nil +} + +func (rid RowID) UnmarshalContainer(root *share.Root, data []byte) (shwap.Row, error) { + var rowBlk bitswappb.RowBlock + if err := rowBlk.Unmarshal(data); err != nil { + return shwap.Row{}, fmt.Errorf("unmarshaling RowBlock: %w", err) + } + + row := shwap.RowFromProto(rowBlk.Row) + if err := row.Validate(root, rid.RowIndex); err != nil { + return shwap.Row{}, fmt.Errorf("validating Row: %w", err) + } + // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string + // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response + // verification) + return row, nil +} diff --git a/share/shwap/p2p/bitswap/row_namespace_data.go b/share/shwap/p2p/bitswap/row_namespace_data.go new file mode 100644 index 0000000000..f9b366956d --- /dev/null +++ b/share/shwap/p2p/bitswap/row_namespace_data.go @@ -0,0 +1,112 @@ +package bitswap + +import ( + "fmt" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/shwap" + bitswapb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" +) + +const ( + // rowNamespaceDataCodec is a CID codec used for data Bitswap requests over Namespaced Merkle Tree. + rowNamespaceDataCodec = 0x7820 + + // rowNamespaceDataMultihashCode is the multihash code for data multihash function. + rowNamespaceDataMultihashCode = 0x7821 +) + +func init() { + RegisterID( + rowNamespaceDataMultihashCode, + rowNamespaceDataCodec, + shwap.RowNamespaceDataIDSize, + func(cid cid.Cid) (blockBuilder, error) { + return RowNamespaceDataIDFromCID(cid) + }, + ) +} + +type RowNamespaceDataID shwap.RowNamespaceDataID + +// RowNamespaceDataIDFromCID coverts CID to RowNamespaceDataID. +func RowNamespaceDataIDFromCID(cid cid.Cid) (RowNamespaceDataID, error) { + rndidData, err := extractCID(cid) + if err != nil { + return RowNamespaceDataID{}, err + } + + rndid, err := shwap.RowNamespaceDataIDFromBinary(rndidData) + if err != nil { + return RowNamespaceDataID{}, fmt.Errorf("unmarhalling RowNamespaceDataID: %w", err) + } + + return RowNamespaceDataID(rndid), nil +} + +func (rndid RowNamespaceDataID) String() string { + data, err := rndid.MarshalBinary() + if err != nil { + panic(fmt.Errorf("marshaling RowNamespaceDataID: %w", err)) + } + + return string(data) +} + +func (rndid RowNamespaceDataID) MarshalBinary() ([]byte, error) { + return shwap.RowNamespaceDataID(rndid).MarshalBinary() +} + +func (rndid RowNamespaceDataID) CID() cid.Cid { + return encodeCID(shwap.RowNamespaceDataID(rndid), rowNamespaceDataMultihashCode, rowNamespaceDataCodec) +} + +func (rndid RowNamespaceDataID) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { + rnd, err := shwap.RowNamespaceDataFromEDS(eds, rndid.DataNamespace, rndid.RowIndex) + if err != nil { + return nil, err + } + + dataID, err := rndid.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("marshaling RowNamespaceDataID: %w", err) + } + + rndidBlk := bitswapb.RowNamespaceDataBlock{ + RowNamespaceDataId: dataID, + Data: rnd.ToProto(), + } + + dataBlk, err := rndidBlk.Marshal() + if err != nil { + return nil, fmt.Errorf("marshaling RowNamespaceDataBlock: %w", err) + } + + blk, err := blocks.NewBlockWithCid(dataBlk, rndid.CID()) + if err != nil { + return nil, fmt.Errorf("assembling block: %w", err) + } + + return blk, nil +} + +func (rndid RowNamespaceDataID) UnmarshalContainer(root *share.Root, data []byte) (shwap.RowNamespaceData, error) { + var rndBlk bitswapb.RowNamespaceDataBlock + if err := rndBlk.Unmarshal(data); err != nil { + return shwap.RowNamespaceData{}, fmt.Errorf("unmarshaling RowNamespaceDataBlock: %w", err) + } + + rnd := shwap.RowNamespaceDataFromProto(rndBlk.Data) + if err := rnd.Validate(root, rndid.DataNamespace, rndid.RowIndex); err != nil { + return shwap.RowNamespaceData{}, fmt.Errorf("validating RowNamespaceData: %w", err) + } + // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string + // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response + // verification) + return rnd, nil +} diff --git a/share/shwap/p2p/bitswap/row_namespace_data_test.go b/share/shwap/p2p/bitswap/row_namespace_data_test.go new file mode 100644 index 0000000000..fb59219f1b --- /dev/null +++ b/share/shwap/p2p/bitswap/row_namespace_data_test.go @@ -0,0 +1,41 @@ +package bitswap + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/sharetest" + "github.com/celestiaorg/celestia-node/share/shwap" +) + +func TestRowNamespaceDataRoundtrip_GetContainers(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + namespace := sharetest.RandV0Namespace() + eds, root := edstest.RandEDSWithNamespace(t, namespace, 64, 16) + client := remoteClient(ctx, t, newTestBlockstore(eds)) + + rowIdxs := share.RowsWithNamespace(root, namespace) + ids := make([]ID[shwap.RowNamespaceData], len(rowIdxs)) + for i, rowIdx := range rowIdxs { + rid, err := shwap.NewRowNamespaceDataID(1, rowIdx, namespace, root) + require.NoError(t, err) + ids[i] = RowNamespaceDataID(rid) + } + + cntrs, err := GetContainers(ctx, client, root, ids...) + require.NoError(t, err) + require.Len(t, cntrs, len(ids)) + + for i, cntr := range cntrs { + sid := ids[i].(RowNamespaceDataID) + err = cntr.Validate(root, sid.DataNamespace, sid.RowIndex) + require.NoError(t, err) + } +} diff --git a/share/shwap/p2p/bitswap/row_test.go b/share/shwap/p2p/bitswap/row_test.go new file mode 100644 index 0000000000..3625513965 --- /dev/null +++ b/share/shwap/p2p/bitswap/row_test.go @@ -0,0 +1,48 @@ +package bitswap + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/shwap" +) + +func TestRowRoundtrip_GetContainers(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + eds := edstest.RandEDS(t, 2) + root, err := share.NewRoot(eds) + require.NoError(t, err) + client := remoteClient(ctx, t, newTestBlockstore(eds)) + + ids := make([]ID[shwap.Row], eds.Width()) + for i := range ids { + rid, err := shwap.NewRowID(1, i, root) + require.NoError(t, err) + log.Debugf("%X", RowID(rid).CID()) + ids[i] = RowID(rid) + } + + cntrs, err := GetContainers(ctx, client, root, ids...) + require.NoError(t, err) + require.Len(t, cntrs, len(ids)) + + for i, cntr := range cntrs { + rid := ids[i].(RowID) + err = cntr.Validate(root, rid.RowIndex) + require.NoError(t, err) + } + + var entries int + globalVerifiers.Range(func(any, any) bool { + entries++ + return true + }) + require.Zero(t, entries) +} diff --git a/share/shwap/p2p/bitswap/sample.go b/share/shwap/p2p/bitswap/sample.go new file mode 100644 index 0000000000..91cb287991 --- /dev/null +++ b/share/shwap/p2p/bitswap/sample.go @@ -0,0 +1,112 @@ +package bitswap + +import ( + "fmt" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/shwap" + bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" +) + +const ( + // sampleCodec is a CID codec used for share sampling Bitswap requests over Namespaced + // Merkle Tree. + sampleCodec = 0x7810 + + // sampleMultihashCode is the multihash code for share sampling multihash function. + sampleMultihashCode = 0x7811 +) + +func init() { + RegisterID( + sampleMultihashCode, + sampleCodec, + shwap.SampleIDSize, + func(cid cid.Cid) (blockBuilder, error) { + return SampleIDFromCID(cid) + }, + ) +} + +type SampleID shwap.SampleID + +// SampleIDFromCID coverts CID to SampleID. +func SampleIDFromCID(cid cid.Cid) (SampleID, error) { + sidData, err := extractCID(cid) + if err != nil { + return SampleID{}, err + } + + sid, err := shwap.SampleIDFromBinary(sidData) + if err != nil { + return SampleID{}, fmt.Errorf("while unmarhaling SampleID: %w", err) + } + + return SampleID(sid), nil +} + +func (sid SampleID) String() string { + data, err := sid.MarshalBinary() + if err != nil { + panic(fmt.Errorf("marshaling SampleID: %w", err)) + } + return string(data) +} + +func (sid SampleID) MarshalBinary() ([]byte, error) { + return shwap.SampleID(sid).MarshalBinary() +} + +func (sid SampleID) CID() cid.Cid { + return encodeCID(shwap.SampleID(sid), sampleMultihashCode, sampleCodec) +} + +func (sid SampleID) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { + smpl, err := shwap.SampleFromEDS(eds, rsmt2d.Row, sid.RowIndex, sid.ShareIndex) + if err != nil { + return nil, err + } + + dataID, err := sid.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("marshaling SampleID: %w", err) + } + + smplBlk := bitswappb.SampleBlock{ + SampleId: dataID, + Sample: smpl.ToProto(), + } + + dataBlk, err := smplBlk.Marshal() + if err != nil { + return nil, fmt.Errorf("marshaling SampleBlock: %w", err) + } + + blk, err := blocks.NewBlockWithCid(dataBlk, sid.CID()) + if err != nil { + return nil, fmt.Errorf("assembling block: %w", err) + } + + return blk, nil +} + +func (sid SampleID) UnmarshalContainer(root *share.Root, data []byte) (shwap.Sample, error) { + var sampleBlk bitswappb.SampleBlock + if err := sampleBlk.Unmarshal(data); err != nil { + return shwap.Sample{}, fmt.Errorf("unmarshaling SampleBlock: %w", err) + } + + sample := shwap.SampleFromProto(sampleBlk.Sample) + if err := sample.Validate(root, sid.RowIndex, sid.ShareIndex); err != nil { + return shwap.Sample{}, fmt.Errorf("validating Sample: %w", err) + } + // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string + // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response + // verification) + return sample, nil +} diff --git a/share/shwap/p2p/bitswap/sample_test.go b/share/shwap/p2p/bitswap/sample_test.go new file mode 100644 index 0000000000..28dad5327d --- /dev/null +++ b/share/shwap/p2p/bitswap/sample_test.go @@ -0,0 +1,43 @@ +package bitswap + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/shwap" +) + +func TestSampleRoundtrip_GetContainers(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + eds := edstest.RandEDS(t, 8) + root, err := share.NewRoot(eds) + require.NoError(t, err) + client := remoteClient(ctx, t, newTestBlockstore(eds)) + + var ids []ID[shwap.Sample] + width := int(eds.Width()) + for x := 0; x < width; x++ { + for y := 0; y < width; y++ { + sid, err := shwap.NewSampleID(1, x, y, root) + require.NoError(t, err) + ids = append(ids, SampleID(sid)) + } + } + + cntrs, err := GetContainers(ctx, client, root, ids...) + require.NoError(t, err) + require.Len(t, cntrs, len(ids)) + + for i, cntr := range cntrs { + sid := ids[i].(SampleID) + err = cntr.Validate(root, sid.RowIndex, sid.ShareIndex) + require.NoError(t, err) + } +} From 7d14e2bb264cb69faa4e9c8cb2a857cb6f2dcc72 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 27 May 2024 18:41:38 +0300 Subject: [PATCH 02/40] remove generics and type assert --- share/shwap/p2p/bitswap/bitswap.go | 27 ++++++---------- share/shwap/p2p/bitswap/row.go | 32 ++++++++++++------- share/shwap/p2p/bitswap/row_namespace_data.go | 32 ++++++++++++------- .../p2p/bitswap/row_namespace_data_test.go | 15 +++++---- share/shwap/p2p/bitswap/row_test.go | 16 +++++----- share/shwap/p2p/bitswap/sample.go | 31 +++++++++++------- share/shwap/p2p/bitswap/sample_test.go | 23 +++++++------ 7 files changed, 96 insertions(+), 80 deletions(-) diff --git a/share/shwap/p2p/bitswap/bitswap.go b/share/shwap/p2p/bitswap/bitswap.go index 6ffa2cb8ab..5a62c25947 100644 --- a/share/shwap/p2p/bitswap/bitswap.go +++ b/share/shwap/p2p/bitswap/bitswap.go @@ -25,12 +25,14 @@ var log = logger.Logger("shwap/bitswap") // * godoc // * document steps required to add new id/container type -type ID[C any] interface { +type ID interface { String() string CID() cid.Cid - UnmarshalContainer(*share.Root, []byte) (C, error) + Verifier(root *share.Root) verify } +type verify func(data []byte) error + func RegisterID(mhcode, codec uint64, size int, bldrFn func(cid2 cid.Cid) (blockBuilder, error)) { mh.Register(mhcode, func() hash.Hash { return &hasher{IDSize: size} @@ -45,33 +47,24 @@ func RegisterID(mhcode, codec uint64, size int, bldrFn func(cid2 cid.Cid) (block // GetContainers // Does not guarantee synchronization. Calling this func simultaneously with the same ID may cause // issues. TODO: Describe motivation -func GetContainers[C any](ctx context.Context, fetcher exchange.Fetcher, root *share.Root, ids ...ID[C]) ([]C, error) { +func GetContainers(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, ids ...ID) error { ctx, cancel := context.WithCancel(ctx) defer cancel() cids := make([]cid.Cid, len(ids)) - cntrs := make([]C, len(ids)) for i, id := range ids { i := i cids[i] = id.CID() idStr := id.String() - globalVerifiers.add(idStr, func(data []byte) error { - cntr, err := id.UnmarshalContainer(root, data) - if err != nil { - return err - } - - cntrs[i] = cntr - return nil - }) + globalVerifiers.add(idStr, id.Verifier(root)) defer globalVerifiers.release(idStr) } // must start getting only after verifiers are registered blkCh, err := fetcher.GetBlocks(ctx, cids) if err != nil { - return nil, fmt.Errorf("fetching bitswap blocks: %w", err) + return fmt.Errorf("fetching bitswap blocks: %w", err) } // GetBlocks handles ctx and closes blkCh, so we don't have to @@ -81,12 +74,12 @@ func GetContainers[C any](ctx context.Context, fetcher exchange.Fetcher, root *s } if len(blks) != len(cids) { if ctx.Err() != nil { - return nil, ctx.Err() + return ctx.Err() } - return nil, fmt.Errorf("not all the containers were found") + return fmt.Errorf("not all the containers were found") } - return cntrs, nil + return nil } var globalVerifiers verifiers diff --git a/share/shwap/p2p/bitswap/row.go b/share/shwap/p2p/bitswap/row.go index 2cc58dc2b8..cfa10d415d 100644 --- a/share/shwap/p2p/bitswap/row.go +++ b/share/shwap/p2p/bitswap/row.go @@ -90,18 +90,26 @@ func (rid RowID) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, err return blk, nil } -func (rid RowID) UnmarshalContainer(root *share.Root, data []byte) (shwap.Row, error) { - var rowBlk bitswappb.RowBlock - if err := rowBlk.Unmarshal(data); err != nil { - return shwap.Row{}, fmt.Errorf("unmarshaling RowBlock: %w", err) - } +type RowBlock struct { + RowID + Row shwap.Row +} - row := shwap.RowFromProto(rowBlk.Row) - if err := row.Validate(root, rid.RowIndex); err != nil { - return shwap.Row{}, fmt.Errorf("validating Row: %w", err) +func (r *RowBlock) Verifier(root *share.Root) verify { + return func(data []byte) error { + var rowBlk bitswappb.RowBlock + if err := rowBlk.Unmarshal(data); err != nil { + return fmt.Errorf("unmarshaling RowBlock: %w", err) + } + + r.Row = shwap.RowFromProto(rowBlk.Row) + if err := r.Row.Validate(root, r.RowID.RowIndex); err != nil { + fmt.Errorf("validating Row: %w", err) + } + + // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string + // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response + // verification) + return nil } - // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string - // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response - // verification) - return row, nil } diff --git a/share/shwap/p2p/bitswap/row_namespace_data.go b/share/shwap/p2p/bitswap/row_namespace_data.go index f9b366956d..595b8b2c34 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data.go +++ b/share/shwap/p2p/bitswap/row_namespace_data.go @@ -95,18 +95,26 @@ func (rndid RowNamespaceDataID) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (bl return blk, nil } -func (rndid RowNamespaceDataID) UnmarshalContainer(root *share.Root, data []byte) (shwap.RowNamespaceData, error) { - var rndBlk bitswapb.RowNamespaceDataBlock - if err := rndBlk.Unmarshal(data); err != nil { - return shwap.RowNamespaceData{}, fmt.Errorf("unmarshaling RowNamespaceDataBlock: %w", err) - } +type RowNamespaceDataBlock struct { + RowNamespaceDataID + Data shwap.RowNamespaceData +} - rnd := shwap.RowNamespaceDataFromProto(rndBlk.Data) - if err := rnd.Validate(root, rndid.DataNamespace, rndid.RowIndex); err != nil { - return shwap.RowNamespaceData{}, fmt.Errorf("validating RowNamespaceData: %w", err) +func (r *RowNamespaceDataBlock) Verifier(root *share.Root) verify { + return func(data []byte) error { + var rndBlk bitswapb.RowNamespaceDataBlock + if err := rndBlk.Unmarshal(data); err != nil { + return fmt.Errorf("unmarshaling RowNamespaceDataBlock: %w", err) + } + + r.Data = shwap.RowNamespaceDataFromProto(rndBlk.Data) + if err := r.Data.Validate(root, r.DataNamespace, r.RowIndex); err != nil { + return fmt.Errorf("validating RowNamespaceData: %w", err) + } + + // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string + // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response + // verification) + return nil } - // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string - // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response - // verification) - return rnd, nil } diff --git a/share/shwap/p2p/bitswap/row_namespace_data_test.go b/share/shwap/p2p/bitswap/row_namespace_data_test.go index fb59219f1b..83d57272f2 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_test.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_test.go @@ -22,20 +22,21 @@ func TestRowNamespaceDataRoundtrip_GetContainers(t *testing.T) { client := remoteClient(ctx, t, newTestBlockstore(eds)) rowIdxs := share.RowsWithNamespace(root, namespace) - ids := make([]ID[shwap.RowNamespaceData], len(rowIdxs)) + nds := make([]*RowNamespaceDataBlock, len(rowIdxs)) + ids := make([]ID, len(rowIdxs)) for i, rowIdx := range rowIdxs { rid, err := shwap.NewRowNamespaceDataID(1, rowIdx, namespace, root) require.NoError(t, err) - ids[i] = RowNamespaceDataID(rid) + //TODO(@walldiss): not sure if copy of RowNamespaceDataID type is needed in bitswap + nds[i] = &RowNamespaceDataBlock{RowNamespaceDataID: RowNamespaceDataID(rid)} + ids[i] = nds[i] } - cntrs, err := GetContainers(ctx, client, root, ids...) + err := GetContainers(ctx, client, root, ids...) require.NoError(t, err) - require.Len(t, cntrs, len(ids)) - for i, cntr := range cntrs { - sid := ids[i].(RowNamespaceDataID) - err = cntr.Validate(root, sid.DataNamespace, sid.RowIndex) + for _, nd := range nds { + err = nd.Data.Validate(root, nd.RowNamespaceDataID.DataNamespace, nd.RowNamespaceDataID.RowIndex) require.NoError(t, err) } } diff --git a/share/shwap/p2p/bitswap/row_test.go b/share/shwap/p2p/bitswap/row_test.go index 3625513965..b962b7f179 100644 --- a/share/shwap/p2p/bitswap/row_test.go +++ b/share/shwap/p2p/bitswap/row_test.go @@ -16,26 +16,26 @@ func TestRowRoundtrip_GetContainers(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - eds := edstest.RandEDS(t, 2) + eds := edstest.RandEDS(t, 4) root, err := share.NewRoot(eds) require.NoError(t, err) client := remoteClient(ctx, t, newTestBlockstore(eds)) - ids := make([]ID[shwap.Row], eds.Width()) + ids := make([]ID, eds.Width()) + data := make([]*RowBlock, eds.Width()) for i := range ids { rid, err := shwap.NewRowID(1, i, root) require.NoError(t, err) log.Debugf("%X", RowID(rid).CID()) - ids[i] = RowID(rid) + data[i] = &RowBlock{RowID: RowID(rid)} + ids[i] = data[i] } - cntrs, err := GetContainers(ctx, client, root, ids...) + err = GetContainers(ctx, client, root, ids...) require.NoError(t, err) - require.Len(t, cntrs, len(ids)) - for i, cntr := range cntrs { - rid := ids[i].(RowID) - err = cntr.Validate(root, rid.RowIndex) + for _, row := range data { + err = row.Row.Validate(root, row.RowIndex) require.NoError(t, err) } diff --git a/share/shwap/p2p/bitswap/sample.go b/share/shwap/p2p/bitswap/sample.go index 91cb287991..15b41c8229 100644 --- a/share/shwap/p2p/bitswap/sample.go +++ b/share/shwap/p2p/bitswap/sample.go @@ -95,18 +95,25 @@ func (sid SampleID) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, return blk, nil } -func (sid SampleID) UnmarshalContainer(root *share.Root, data []byte) (shwap.Sample, error) { - var sampleBlk bitswappb.SampleBlock - if err := sampleBlk.Unmarshal(data); err != nil { - return shwap.Sample{}, fmt.Errorf("unmarshaling SampleBlock: %w", err) - } +type SampleBlock struct { + SampleID + Sample shwap.Sample +} - sample := shwap.SampleFromProto(sampleBlk.Sample) - if err := sample.Validate(root, sid.RowIndex, sid.ShareIndex); err != nil { - return shwap.Sample{}, fmt.Errorf("validating Sample: %w", err) +func (s *SampleBlock) Verifier(root *share.Root) verify { + return func(data []byte) error { + var sampleBlk bitswappb.SampleBlock + if err := sampleBlk.Unmarshal(data); err != nil { + return fmt.Errorf("unmarshaling SampleBlock: %w", err) + } + + s.Sample = shwap.SampleFromProto(sampleBlk.Sample) + if err := s.Sample.Validate(root, s.RowIndex, s.ShareIndex); err != nil { + return fmt.Errorf("validating Sample: %w", err) + } + // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string + // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response + // verification) + return nil } - // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string - // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response - // verification) - return sample, nil } diff --git a/share/shwap/p2p/bitswap/sample_test.go b/share/shwap/p2p/bitswap/sample_test.go index 28dad5327d..39f94a9b34 100644 --- a/share/shwap/p2p/bitswap/sample_test.go +++ b/share/shwap/p2p/bitswap/sample_test.go @@ -2,14 +2,12 @@ package bitswap import ( "context" - "testing" - "time" - - "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/edstest" "github.com/celestiaorg/celestia-node/share/shwap" + "github.com/stretchr/testify/require" + "testing" + "time" ) func TestSampleRoundtrip_GetContainers(t *testing.T) { @@ -21,23 +19,24 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { require.NoError(t, err) client := remoteClient(ctx, t, newTestBlockstore(eds)) - var ids []ID[shwap.Sample] width := int(eds.Width()) + ids := make([]ID, 0, width*width) + samples := make([]*SampleBlock, 0, width*width) for x := 0; x < width; x++ { for y := 0; y < width; y++ { sid, err := shwap.NewSampleID(1, x, y, root) require.NoError(t, err) - ids = append(ids, SampleID(sid)) + sampleBlock := &SampleBlock{SampleID: SampleID(sid)} + ids = append(ids, sampleBlock) + samples = append(samples, sampleBlock) } } - cntrs, err := GetContainers(ctx, client, root, ids...) + err = GetContainers(ctx, client, root, ids...) require.NoError(t, err) - require.Len(t, cntrs, len(ids)) - for i, cntr := range cntrs { - sid := ids[i].(SampleID) - err = cntr.Validate(root, sid.RowIndex, sid.ShareIndex) + for _, sample := range samples { + err = sample.Sample.Validate(root, sample.RowIndex, sample.ShareIndex) require.NoError(t, err) } } From 3041174428708c206c9962bbceba56c2f1610f4c Mon Sep 17 00:00:00 2001 From: Wondertan Date: Wed, 29 May 2024 01:14:03 +0200 Subject: [PATCH 03/40] cleaning progress --- share/shwap/p2p/bitswap/bitswap.go | 82 +++++++------------ .../p2p/bitswap/{id_registry.go => cid.go} | 15 ---- share/shwap/p2p/bitswap/hasher.go | 11 +-- share/shwap/p2p/bitswap/registry.go | 36 ++++++++ share/shwap/p2p/bitswap/row.go | 76 +++++++++-------- share/shwap/p2p/bitswap/row_namespace_data.go | 79 ++++++++++-------- .../p2p/bitswap/row_namespace_data_test.go | 17 ++-- share/shwap/p2p/bitswap/row_test.go | 22 +++-- share/shwap/p2p/bitswap/sample.go | 74 +++++++++-------- share/shwap/p2p/bitswap/sample_test.go | 25 +++--- share/shwap/sample_id.go | 2 +- 11 files changed, 231 insertions(+), 208 deletions(-) rename share/shwap/p2p/bitswap/{id_registry.go => cid.go} (82%) create mode 100644 share/shwap/p2p/bitswap/registry.go diff --git a/share/shwap/p2p/bitswap/bitswap.go b/share/shwap/p2p/bitswap/bitswap.go index 5a62c25947..b8143076b9 100644 --- a/share/shwap/p2p/bitswap/bitswap.go +++ b/share/shwap/p2p/bitswap/bitswap.go @@ -3,14 +3,12 @@ package bitswap import ( "context" "fmt" - "hash" "sync" "github.com/ipfs/boxo/exchange" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" logger "github.com/ipfs/go-log/v2" - mh "github.com/multiformats/go-multihash" "github.com/celestiaorg/celestia-node/share" ) @@ -18,47 +16,47 @@ import ( var log = logger.Logger("shwap/bitswap") // TODO: -// * Synchronization for GetContainers +// * Synchronization for Fetch // * Test with race and count 100 // * Hasher test // * Coverage // * godoc // * document steps required to add new id/container type -type ID interface { - String() string - CID() cid.Cid - Verifier(root *share.Root) verify -} +// PopulateFn is a closure that validates given bytes and populates +// Blocks with serialized shwap container in those bytes on success. +type PopulateFn func([]byte) error -type verify func(data []byte) error +type Block interface { + blockBuilder -func RegisterID(mhcode, codec uint64, size int, bldrFn func(cid2 cid.Cid) (blockBuilder, error)) { - mh.Register(mhcode, func() hash.Hash { - return &hasher{IDSize: size} - }) - specRegistry[mhcode] = idSpec{ - size: size, - codec: codec, - builder: bldrFn, - } + // String returns string representation of the Block + // to be used as map key. Might not be human-readable + String() string + // CID returns shwap ID of the Block formatted as CID. + CID() cid.Cid + // IsEmpty reports whether the Block has the shwap container. + // If the Block is empty, it can be populated with Fetch. + IsEmpty() bool + // Populate returns closure that fills up the Block with shwap container. + // Population involves data validation against the Root. + Populate(*share.Root) PopulateFn } -// GetContainers -// Does not guarantee synchronization. Calling this func simultaneously with the same ID may cause -// issues. TODO: Describe motivation -func GetContainers(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, ids ...ID) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - cids := make([]cid.Cid, len(ids)) - for i, id := range ids { - i := i - cids[i] = id.CID() +// Fetch +// Does not guarantee synchronization. Calling this func simultaneously with the same Block may +// cause issues. TODO: Describe motivation +func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, ids ...Block) error { + cids := make([]cid.Cid, 0, len(ids)) + for _, id := range ids { + if !id.IsEmpty() { + continue + } + cids = append(cids, id.CID()) idStr := id.String() - globalVerifiers.add(idStr, id.Verifier(root)) - defer globalVerifiers.release(idStr) + populators.Store(idStr, id.Populate(root)) + defer populators.Delete(idStr) } // must start getting only after verifiers are registered @@ -82,24 +80,4 @@ func GetContainers(ctx context.Context, fetcher exchange.Fetcher, root *share.Ro return nil } -var globalVerifiers verifiers - -type verifiers struct { - sync.Map -} - -func (vs *verifiers) add(key string, v func([]byte) error) { - vs.Store(key, v) -} - -func (vs *verifiers) get(key string) func([]byte) error { - v, ok := vs.Load(key) - if !ok { - return nil - } - return v.(func([]byte) error) -} - -func (vs *verifiers) release(key string) { - vs.Delete(key) -} +var populators sync.Map diff --git a/share/shwap/p2p/bitswap/id_registry.go b/share/shwap/p2p/bitswap/cid.go similarity index 82% rename from share/shwap/p2p/bitswap/id_registry.go rename to share/shwap/p2p/bitswap/cid.go index 6d9ced3c69..a758dc9cfa 100644 --- a/share/shwap/p2p/bitswap/id_registry.go +++ b/share/shwap/p2p/bitswap/cid.go @@ -4,25 +4,10 @@ import ( "encoding" "fmt" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" mh "github.com/multiformats/go-multihash" - - "github.com/celestiaorg/rsmt2d" ) -var specRegistry = make(map[uint64]idSpec) - -type idSpec struct { - size int - codec uint64 - builder func(cid.Cid) (blockBuilder, error) -} - -type blockBuilder interface { - BlockFromEDS(*rsmt2d.ExtendedDataSquare) (blocks.Block, error) -} - // DefaultAllowlist keeps default list of multihashes allowed in the network. // TODO(@Wondertan): Make it private and instead provide Blockservice constructor with injected // allowlist diff --git a/share/shwap/p2p/bitswap/hasher.go b/share/shwap/p2p/bitswap/hasher.go index 8861a77ad9..1bb85bbf49 100644 --- a/share/shwap/p2p/bitswap/hasher.go +++ b/share/shwap/p2p/bitswap/hasher.go @@ -20,26 +20,27 @@ func (h *hasher) Write(data []byte) (int, error) { log.Error() return 0, err } - // extract ID out of data + // extract Block out of data // we do this on the raw data to: // * Avoid complicating hasher with generalized bytes -> type unmarshalling // * Avoid type allocations id := data[pbOffset : h.IDSize+pbOffset] // get registered verifier and use it to check data validity - ver := globalVerifiers.get(string(id)) - if ver == nil { + val, ok := populators.Load(string(id)) + if !ok { err := fmt.Errorf("shwap/bitswap hasher: no verifier registered") log.Error(err) return 0, err } - err := ver(data) + populate := val.(PopulateFn) + err := populate(data) if err != nil { err = fmt.Errorf("shwap/bitswap hasher: verifying data: %w", err) log.Error(err) return 0, err } // if correct set the id as resulting sum - // it's required for the sum to match the original ID + // it's required for the sum to match the original Block // to satisfy hash contract h.sum = id return len(data), err diff --git a/share/shwap/p2p/bitswap/registry.go b/share/shwap/p2p/bitswap/registry.go new file mode 100644 index 0000000000..66a2a3ff97 --- /dev/null +++ b/share/shwap/p2p/bitswap/registry.go @@ -0,0 +1,36 @@ +package bitswap + +import ( + "hash" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" + + "github.com/celestiaorg/rsmt2d" +) + +// RegisterBlock registers the new Block type. +func RegisterBlock(mhcode, codec uint64, size int, bldrFn func(cid.Cid) (blockBuilder, error)) { + mh.Register(mhcode, func() hash.Hash { + return &hasher{IDSize: size} + }) + specRegistry[mhcode] = idSpec{ + size: size, + codec: codec, + builder: bldrFn, + } +} + +var specRegistry = make(map[uint64]idSpec) + +type idSpec struct { + size int + codec uint64 + builder func(cid.Cid) (blockBuilder, error) +} + +type blockBuilder interface { + // BlockFromEDS gets Bitswap Block out of the EDS. + BlockFromEDS(*rsmt2d.ExtendedDataSquare) (blocks.Block, error) +} diff --git a/share/shwap/p2p/bitswap/row.go b/share/shwap/p2p/bitswap/row.go index cfa10d415d..cf845816bf 100644 --- a/share/shwap/p2p/bitswap/row.go +++ b/share/shwap/p2p/bitswap/row.go @@ -22,67 +22,79 @@ const ( ) func init() { - RegisterID( + RegisterBlock( rowMultihashCode, rowCodec, shwap.RowIDSize, func(cid cid.Cid) (blockBuilder, error) { - return RowIDFromCID(cid) + return EmptyRowBlockFromCID(cid) }, ) } -type RowID shwap.RowID +type RowBlock struct { + ID shwap.RowID + Container *shwap.Row +} + +func NewEmptyRowBlock(height uint64, rowIdx int, root *share.Root) (*RowBlock, error) { + id, err := shwap.NewRowID(height, rowIdx, root) + if err != nil { + return nil, err + } + + return &RowBlock{ID: id}, nil +} -// RowIDFromCID coverts CID to RowID. -func RowIDFromCID(cid cid.Cid) (RowID, error) { +// EmptyRowBlockFromCID coverts CID to RowBlock. +func EmptyRowBlockFromCID(cid cid.Cid) (*RowBlock, error) { ridData, err := extractCID(cid) if err != nil { - return RowID{}, err + return nil, err } rid, err := shwap.RowIDFromBinary(ridData) if err != nil { - return RowID{}, fmt.Errorf("while unmarhaling RowID: %w", err) + return nil, fmt.Errorf("while unmarhaling RowBlock: %w", err) } - return RowID(rid), nil + return &RowBlock{ID: rid}, nil } -func (rid RowID) String() string { - data, err := rid.MarshalBinary() +func (rb *RowBlock) IsEmpty() bool { + return rb.Container == nil +} + +func (rb *RowBlock) String() string { + data, err := rb.ID.MarshalBinary() if err != nil { - panic(fmt.Errorf("marshaling RowID: %w", err)) + panic(fmt.Errorf("marshaling RowBlock: %w", err)) } return string(data) } -func (rid RowID) MarshalBinary() ([]byte, error) { - return shwap.RowID(rid).MarshalBinary() -} - -func (rid RowID) CID() cid.Cid { - return encodeCID(rid, rowMultihashCode, rowCodec) +func (rb *RowBlock) CID() cid.Cid { + return encodeCID(rb.ID, rowMultihashCode, rowCodec) } -func (rid RowID) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { - row := shwap.RowFromEDS(eds, rid.RowIndex, shwap.Left) +func (rb *RowBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { + row := shwap.RowFromEDS(eds, rb.ID.RowIndex, shwap.Left) - dataID, err := rid.MarshalBinary() + rowID, err := rb.ID.MarshalBinary() if err != nil { - return nil, fmt.Errorf("marshaling RowID: %w", err) + return nil, fmt.Errorf("marshaling RowBlock: %w", err) } rowBlk := bitswappb.RowBlock{ - RowId: dataID, + RowId: rowID, Row: row.ToProto(), } - dataBlk, err := rowBlk.Marshal() + blkData, err := rowBlk.Marshal() if err != nil { return nil, fmt.Errorf("marshaling RowBlock: %w", err) } - blk, err := blocks.NewBlockWithCid(dataBlk, rid.CID()) + blk, err := blocks.NewBlockWithCid(blkData, rb.CID()) if err != nil { return nil, fmt.Errorf("assembling block: %w", err) } @@ -90,24 +102,20 @@ func (rid RowID) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, err return blk, nil } -type RowBlock struct { - RowID - Row shwap.Row -} - -func (r *RowBlock) Verifier(root *share.Root) verify { +func (rb *RowBlock) Populate(root *share.Root) PopulateFn { return func(data []byte) error { var rowBlk bitswappb.RowBlock if err := rowBlk.Unmarshal(data); err != nil { return fmt.Errorf("unmarshaling RowBlock: %w", err) } - r.Row = shwap.RowFromProto(rowBlk.Row) - if err := r.Row.Validate(root, r.RowID.RowIndex); err != nil { - fmt.Errorf("validating Row: %w", err) + cntr := shwap.RowFromProto(rowBlk.Row) + if err := cntr.Validate(root, rb.ID.RowIndex); err != nil { + return fmt.Errorf("validating Row: %w", err) } + rb.Container = &cntr - // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string + // NOTE: We don't have to validate Block in the RowBlock, as it's implicitly verified by string // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response // verification) return nil diff --git a/share/shwap/p2p/bitswap/row_namespace_data.go b/share/shwap/p2p/bitswap/row_namespace_data.go index 595b8b2c34..482954f4e0 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data.go +++ b/share/shwap/p2p/bitswap/row_namespace_data.go @@ -22,72 +22,89 @@ const ( ) func init() { - RegisterID( + RegisterBlock( rowNamespaceDataMultihashCode, rowNamespaceDataCodec, shwap.RowNamespaceDataIDSize, func(cid cid.Cid) (blockBuilder, error) { - return RowNamespaceDataIDFromCID(cid) + return EmptyRowNamespaceDataBlockFromCID(cid) }, ) } -type RowNamespaceDataID shwap.RowNamespaceDataID +type RowNamespaceDataBlock struct { + ID shwap.RowNamespaceDataID + Container *shwap.RowNamespaceData +} -// RowNamespaceDataIDFromCID coverts CID to RowNamespaceDataID. -func RowNamespaceDataIDFromCID(cid cid.Cid) (RowNamespaceDataID, error) { +func NewEmptyRowNamespaceDataBlock( + height uint64, + rowIdx int, + namespace share.Namespace, + root *share.Root, +) (*RowNamespaceDataBlock, error) { + id, err := shwap.NewRowNamespaceDataID(height, rowIdx, namespace, root) + if err != nil { + return nil, err + } + + return &RowNamespaceDataBlock{ID: id}, nil +} + +// EmptyRowNamespaceDataBlockFromCID coverts CID to RowNamespaceDataBlock. +func EmptyRowNamespaceDataBlockFromCID(cid cid.Cid) (*RowNamespaceDataBlock, error) { rndidData, err := extractCID(cid) if err != nil { - return RowNamespaceDataID{}, err + return nil, err } rndid, err := shwap.RowNamespaceDataIDFromBinary(rndidData) if err != nil { - return RowNamespaceDataID{}, fmt.Errorf("unmarhalling RowNamespaceDataID: %w", err) + return nil, fmt.Errorf("unmarhalling RowNamespaceDataBlock: %w", err) } - return RowNamespaceDataID(rndid), nil + return &RowNamespaceDataBlock{ID: rndid}, nil } -func (rndid RowNamespaceDataID) String() string { - data, err := rndid.MarshalBinary() +func (rndb *RowNamespaceDataBlock) IsEmpty() bool { + return rndb.Container == nil +} + +func (rndb *RowNamespaceDataBlock) String() string { + data, err := rndb.ID.MarshalBinary() if err != nil { - panic(fmt.Errorf("marshaling RowNamespaceDataID: %w", err)) + panic(fmt.Errorf("marshaling RowNamespaceDataBlock: %w", err)) } return string(data) } -func (rndid RowNamespaceDataID) MarshalBinary() ([]byte, error) { - return shwap.RowNamespaceDataID(rndid).MarshalBinary() -} - -func (rndid RowNamespaceDataID) CID() cid.Cid { - return encodeCID(shwap.RowNamespaceDataID(rndid), rowNamespaceDataMultihashCode, rowNamespaceDataCodec) +func (rndb *RowNamespaceDataBlock) CID() cid.Cid { + return encodeCID(rndb.ID, rowNamespaceDataMultihashCode, rowNamespaceDataCodec) } -func (rndid RowNamespaceDataID) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { - rnd, err := shwap.RowNamespaceDataFromEDS(eds, rndid.DataNamespace, rndid.RowIndex) +func (rndb *RowNamespaceDataBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { + rnd, err := shwap.RowNamespaceDataFromEDS(eds, rndb.ID.DataNamespace, rndb.ID.RowIndex) if err != nil { return nil, err } - dataID, err := rndid.MarshalBinary() + rndID, err := rndb.ID.MarshalBinary() if err != nil { - return nil, fmt.Errorf("marshaling RowNamespaceDataID: %w", err) + return nil, fmt.Errorf("marshaling RowNamespaceDataBlock: %w", err) } rndidBlk := bitswapb.RowNamespaceDataBlock{ - RowNamespaceDataId: dataID, + RowNamespaceDataId: rndID, Data: rnd.ToProto(), } - dataBlk, err := rndidBlk.Marshal() + blkData, err := rndidBlk.Marshal() if err != nil { return nil, fmt.Errorf("marshaling RowNamespaceDataBlock: %w", err) } - blk, err := blocks.NewBlockWithCid(dataBlk, rndid.CID()) + blk, err := blocks.NewBlockWithCid(blkData, rndb.CID()) if err != nil { return nil, fmt.Errorf("assembling block: %w", err) } @@ -95,24 +112,20 @@ func (rndid RowNamespaceDataID) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (bl return blk, nil } -type RowNamespaceDataBlock struct { - RowNamespaceDataID - Data shwap.RowNamespaceData -} - -func (r *RowNamespaceDataBlock) Verifier(root *share.Root) verify { +func (rndb *RowNamespaceDataBlock) Populate(root *share.Root) PopulateFn { return func(data []byte) error { var rndBlk bitswapb.RowNamespaceDataBlock if err := rndBlk.Unmarshal(data); err != nil { return fmt.Errorf("unmarshaling RowNamespaceDataBlock: %w", err) } - r.Data = shwap.RowNamespaceDataFromProto(rndBlk.Data) - if err := r.Data.Validate(root, r.DataNamespace, r.RowIndex); err != nil { + cntr := shwap.RowNamespaceDataFromProto(rndBlk.Data) + if err := cntr.Validate(root, rndb.ID.DataNamespace, rndb.ID.RowIndex); err != nil { return fmt.Errorf("validating RowNamespaceData: %w", err) } + rndb.Container = &cntr - // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string + // NOTE: We don't have to validate Block in the RowBlock, as it's implicitly verified by string // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response // verification) return nil diff --git a/share/shwap/p2p/bitswap/row_namespace_data_test.go b/share/shwap/p2p/bitswap/row_namespace_data_test.go index 83d57272f2..bf5a7259a3 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_test.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_test.go @@ -10,7 +10,6 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/edstest" "github.com/celestiaorg/celestia-node/share/sharetest" - "github.com/celestiaorg/celestia-node/share/shwap" ) func TestRowNamespaceDataRoundtrip_GetContainers(t *testing.T) { @@ -22,21 +21,19 @@ func TestRowNamespaceDataRoundtrip_GetContainers(t *testing.T) { client := remoteClient(ctx, t, newTestBlockstore(eds)) rowIdxs := share.RowsWithNamespace(root, namespace) - nds := make([]*RowNamespaceDataBlock, len(rowIdxs)) - ids := make([]ID, len(rowIdxs)) + blks := make([]Block, len(rowIdxs)) for i, rowIdx := range rowIdxs { - rid, err := shwap.NewRowNamespaceDataID(1, rowIdx, namespace, root) + blk, err := NewEmptyRowNamespaceDataBlock(1, rowIdx, namespace, root) require.NoError(t, err) - //TODO(@walldiss): not sure if copy of RowNamespaceDataID type is needed in bitswap - nds[i] = &RowNamespaceDataBlock{RowNamespaceDataID: RowNamespaceDataID(rid)} - ids[i] = nds[i] + blks[i] = blk } - err := GetContainers(ctx, client, root, ids...) + err := Fetch(ctx, client, root, blks...) require.NoError(t, err) - for _, nd := range nds { - err = nd.Data.Validate(root, nd.RowNamespaceDataID.DataNamespace, nd.RowNamespaceDataID.RowIndex) + for _, blk := range blks { + rnd := blk.(*RowNamespaceDataBlock) + err = rnd.Container.Validate(root, rnd.ID.DataNamespace, rnd.ID.RowIndex) require.NoError(t, err) } } diff --git a/share/shwap/p2p/bitswap/row_test.go b/share/shwap/p2p/bitswap/row_test.go index b962b7f179..5d7c2d5d9c 100644 --- a/share/shwap/p2p/bitswap/row_test.go +++ b/share/shwap/p2p/bitswap/row_test.go @@ -9,7 +9,6 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/edstest" - "github.com/celestiaorg/celestia-node/share/shwap" ) func TestRowRoundtrip_GetContainers(t *testing.T) { @@ -21,26 +20,25 @@ func TestRowRoundtrip_GetContainers(t *testing.T) { require.NoError(t, err) client := remoteClient(ctx, t, newTestBlockstore(eds)) - ids := make([]ID, eds.Width()) - data := make([]*RowBlock, eds.Width()) - for i := range ids { - rid, err := shwap.NewRowID(1, i, root) + blks := make([]Block, eds.Width()) + for i := range blks { + blk, err := NewEmptyRowBlock(1, i, root) require.NoError(t, err) - log.Debugf("%X", RowID(rid).CID()) - data[i] = &RowBlock{RowID: RowID(rid)} - ids[i] = data[i] + blks[i] = blk } - err = GetContainers(ctx, client, root, ids...) + err = Fetch(ctx, client, root, blks...) require.NoError(t, err) - for _, row := range data { - err = row.Row.Validate(root, row.RowIndex) + for _, blk := range blks { + row := blk.(*RowBlock) + err = row.Container.Validate(root, row.ID.RowIndex) require.NoError(t, err) } + // TODO: Should be part of a different test var entries int - globalVerifiers.Range(func(any, any) bool { + populators.Range(func(any, any) bool { entries++ return true }) diff --git a/share/shwap/p2p/bitswap/sample.go b/share/shwap/p2p/bitswap/sample.go index 15b41c8229..b7bc7f8245 100644 --- a/share/shwap/p2p/bitswap/sample.go +++ b/share/shwap/p2p/bitswap/sample.go @@ -23,71 +23,83 @@ const ( ) func init() { - RegisterID( + RegisterBlock( sampleMultihashCode, sampleCodec, shwap.SampleIDSize, func(cid cid.Cid) (blockBuilder, error) { - return SampleIDFromCID(cid) + return EmptySampleBlockFromCID(cid) }, ) } -type SampleID shwap.SampleID +type SampleBlock struct { + ID shwap.SampleID + Container *shwap.Sample +} -// SampleIDFromCID coverts CID to SampleID. -func SampleIDFromCID(cid cid.Cid) (SampleID, error) { +func NewEmptySampleBlock(height uint64, rowIdx, colIdx int, root *share.Root) (*SampleBlock, error) { + id, err := shwap.NewSampleID(height, rowIdx, colIdx, root) + if err != nil { + return nil, err + } + + return &SampleBlock{ID: id}, nil +} + +// EmptySampleBlockFromCID coverts CID to SampleBlock. +func EmptySampleBlockFromCID(cid cid.Cid) (*SampleBlock, error) { sidData, err := extractCID(cid) if err != nil { - return SampleID{}, err + return nil, err } sid, err := shwap.SampleIDFromBinary(sidData) if err != nil { - return SampleID{}, fmt.Errorf("while unmarhaling SampleID: %w", err) + return nil, fmt.Errorf("while unmarhaling SampleBlock: %w", err) } - return SampleID(sid), nil + return &SampleBlock{ID: sid}, nil } -func (sid SampleID) String() string { - data, err := sid.MarshalBinary() +func (sb *SampleBlock) IsEmpty() bool { + return sb.Container == nil +} + +func (sb *SampleBlock) String() string { + data, err := sb.ID.MarshalBinary() if err != nil { - panic(fmt.Errorf("marshaling SampleID: %w", err)) + panic(fmt.Errorf("marshaling SampleBlock: %w", err)) } return string(data) } -func (sid SampleID) MarshalBinary() ([]byte, error) { - return shwap.SampleID(sid).MarshalBinary() -} - -func (sid SampleID) CID() cid.Cid { - return encodeCID(shwap.SampleID(sid), sampleMultihashCode, sampleCodec) +func (sb *SampleBlock) CID() cid.Cid { + return encodeCID(sb.ID, sampleMultihashCode, sampleCodec) } -func (sid SampleID) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { - smpl, err := shwap.SampleFromEDS(eds, rsmt2d.Row, sid.RowIndex, sid.ShareIndex) +func (sb *SampleBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { + smpl, err := shwap.SampleFromEDS(eds, rsmt2d.Row, sb.ID.RowIndex, sb.ID.ShareIndex) if err != nil { return nil, err } - dataID, err := sid.MarshalBinary() + smplID, err := sb.ID.MarshalBinary() if err != nil { - return nil, fmt.Errorf("marshaling SampleID: %w", err) + return nil, fmt.Errorf("marshaling SampleBlock: %w", err) } smplBlk := bitswappb.SampleBlock{ - SampleId: dataID, + SampleId: smplID, Sample: smpl.ToProto(), } - dataBlk, err := smplBlk.Marshal() + blkData, err := smplBlk.Marshal() if err != nil { return nil, fmt.Errorf("marshaling SampleBlock: %w", err) } - blk, err := blocks.NewBlockWithCid(dataBlk, sid.CID()) + blk, err := blocks.NewBlockWithCid(blkData, sb.CID()) if err != nil { return nil, fmt.Errorf("assembling block: %w", err) } @@ -95,23 +107,19 @@ func (sid SampleID) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, return blk, nil } -type SampleBlock struct { - SampleID - Sample shwap.Sample -} - -func (s *SampleBlock) Verifier(root *share.Root) verify { +func (sb *SampleBlock) Populate(root *share.Root) PopulateFn { return func(data []byte) error { var sampleBlk bitswappb.SampleBlock if err := sampleBlk.Unmarshal(data); err != nil { return fmt.Errorf("unmarshaling SampleBlock: %w", err) } - s.Sample = shwap.SampleFromProto(sampleBlk.Sample) - if err := s.Sample.Validate(root, s.RowIndex, s.ShareIndex); err != nil { + cntr := shwap.SampleFromProto(sampleBlk.Sample) + if err := cntr.Validate(root, sb.ID.RowIndex, sb.ID.ShareIndex); err != nil { return fmt.Errorf("validating Sample: %w", err) } - // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string + sb.Container = &cntr + // NOTE: We don't have to validate Block in the RowBlock, as it's implicitly verified by string // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response // verification) return nil diff --git a/share/shwap/p2p/bitswap/sample_test.go b/share/shwap/p2p/bitswap/sample_test.go index 39f94a9b34..6e04eac4d7 100644 --- a/share/shwap/p2p/bitswap/sample_test.go +++ b/share/shwap/p2p/bitswap/sample_test.go @@ -2,12 +2,13 @@ package bitswap import ( "context" - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/eds/edstest" - "github.com/celestiaorg/celestia-node/share/shwap" - "github.com/stretchr/testify/require" "testing" "time" + + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/edstest" ) func TestSampleRoundtrip_GetContainers(t *testing.T) { @@ -20,23 +21,21 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { client := remoteClient(ctx, t, newTestBlockstore(eds)) width := int(eds.Width()) - ids := make([]ID, 0, width*width) - samples := make([]*SampleBlock, 0, width*width) + blks := make([]Block, 0, width*width) for x := 0; x < width; x++ { for y := 0; y < width; y++ { - sid, err := shwap.NewSampleID(1, x, y, root) + blk, err := NewEmptySampleBlock(1, x, y, root) require.NoError(t, err) - sampleBlock := &SampleBlock{SampleID: SampleID(sid)} - ids = append(ids, sampleBlock) - samples = append(samples, sampleBlock) + blks = append(blks, blk) } } - err = GetContainers(ctx, client, root, ids...) + err = Fetch(ctx, client, root, blks...) require.NoError(t, err) - for _, sample := range samples { - err = sample.Sample.Validate(root, sample.RowIndex, sample.ShareIndex) + for _, sample := range blks { + blk := sample.(*SampleBlock) + err = blk.Container.Validate(root, blk.ID.RowIndex, blk.ID.ShareIndex) require.NoError(t, err) } } diff --git a/share/shwap/sample_id.go b/share/shwap/sample_id.go index 33e83fe12d..0febc53ae7 100644 --- a/share/shwap/sample_id.go +++ b/share/shwap/sample_id.go @@ -44,7 +44,7 @@ func NewSampleID(height uint64, rowIdx, colIdx int, root *share.Root) (SampleID, // the expected size. func SampleIDFromBinary(data []byte) (SampleID, error) { if len(data) != SampleIDSize { - return SampleID{}, fmt.Errorf("invalid SampleID data length: expected %d, got %d", SampleIDSize, len(data)) + return SampleID{}, fmt.Errorf("invalid SampleBlock data length: expected %d, got %d", SampleIDSize, len(data)) } rid, err := RowIDFromBinary(data[:RowIDSize]) From b3b64bafd6a63f7e900a1649fadfea64096d1ef7 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Thu, 30 May 2024 11:24:31 +0200 Subject: [PATCH 04/40] duplicates test --- share/shwap/p2p/bitswap/bitswap_test.go | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/share/shwap/p2p/bitswap/bitswap_test.go b/share/shwap/p2p/bitswap/bitswap_test.go index 631c29d19c..3bc5598307 100644 --- a/share/shwap/p2p/bitswap/bitswap_test.go +++ b/share/shwap/p2p/bitswap/bitswap_test.go @@ -3,7 +3,10 @@ package bitswap import ( "context" "fmt" + "math/rand/v2" + "sync" "testing" + "time" "github.com/ipfs/boxo/bitswap" "github.com/ipfs/boxo/bitswap/network" @@ -16,11 +19,60 @@ import ( dssync "github.com/ipfs/go-datastore/sync" record "github.com/libp2p/go-libp2p-record" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/logs" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/edstest" ) +func TestFetchDuplicates(t *testing.T) { + logs.SetDebugLogging() + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + eds := edstest.RandEDS(t, 4) + root, err := share.NewRoot(eds) + require.NoError(t, err) + client := remoteClient(ctx, t, newTestBlockstore(eds)) + + var wg sync.WaitGroup + var blks []*RowBlock + for range 100 { + blk, err := NewEmptyRowBlock(1, 0, root) // create the same Block ID + require.NoError(t, err) + blks = append(blks, blk) + + wg.Add(1) + go func() { + rint := rand.IntN(10) + // this sleep ensures fetches aren't started simultaneously allowing to check for edge-cases + time.Sleep(time.Millisecond * time.Duration(rint)) + + err := Fetch(ctx, client, root, blk) + assert.NoError(t, err) + wg.Done() + }() + + } + wg.Wait() + + for _, blk := range blks { + t.Log(blk.IsEmpty()) + assert.False(t, blk.IsEmpty()) + } + + var entries int + populators.mp.Range(func(any, any) bool { + entries++ + return true + }) + require.Zero(t, entries) +} + func remoteClient(ctx context.Context, t *testing.T, bstore blockstore.Blockstore) exchange.Fetcher { net, err := mocknet.FullMeshLinked(2) require.NoError(t, err) From 7cf5c6372374fdb3ba3d15e3fbbff6c1f1b012a3 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 30 May 2024 14:47:22 +0500 Subject: [PATCH 05/40] populate missing containers --- share/shwap/p2p/bitswap/bitswap.go | 8 ++++---- share/shwap/p2p/bitswap/bitswap_test.go | 2 +- share/shwap/p2p/bitswap/row.go | 3 +++ share/shwap/p2p/bitswap/row_namespace_data.go | 3 +++ share/shwap/p2p/bitswap/sample.go | 3 +++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/share/shwap/p2p/bitswap/bitswap.go b/share/shwap/p2p/bitswap/bitswap.go index b8143076b9..064441ad44 100644 --- a/share/shwap/p2p/bitswap/bitswap.go +++ b/share/shwap/p2p/bitswap/bitswap.go @@ -6,7 +6,6 @@ import ( "sync" "github.com/ipfs/boxo/exchange" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" logger "github.com/ipfs/go-log/v2" @@ -66,11 +65,12 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, ids } // GetBlocks handles ctx and closes blkCh, so we don't have to - blks := make([]blocks.Block, 0, len(cids)) + var amount int for blk := range blkCh { - blks = append(blks, blk) + ids[amount].Populate(root)(blk.RawData()) + amount++ } - if len(blks) != len(cids) { + if amount != len(cids) { if ctx.Err() != nil { return ctx.Err() } diff --git a/share/shwap/p2p/bitswap/bitswap_test.go b/share/shwap/p2p/bitswap/bitswap_test.go index 3bc5598307..df92665aeb 100644 --- a/share/shwap/p2p/bitswap/bitswap_test.go +++ b/share/shwap/p2p/bitswap/bitswap_test.go @@ -66,7 +66,7 @@ func TestFetchDuplicates(t *testing.T) { } var entries int - populators.mp.Range(func(any, any) bool { + populators.Range(func(any, any) bool { entries++ return true }) diff --git a/share/shwap/p2p/bitswap/row.go b/share/shwap/p2p/bitswap/row.go index cf845816bf..9fa232d41c 100644 --- a/share/shwap/p2p/bitswap/row.go +++ b/share/shwap/p2p/bitswap/row.go @@ -104,6 +104,9 @@ func (rb *RowBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, func (rb *RowBlock) Populate(root *share.Root) PopulateFn { return func(data []byte) error { + if !rb.IsEmpty() { + return nil + } var rowBlk bitswappb.RowBlock if err := rowBlk.Unmarshal(data); err != nil { return fmt.Errorf("unmarshaling RowBlock: %w", err) diff --git a/share/shwap/p2p/bitswap/row_namespace_data.go b/share/shwap/p2p/bitswap/row_namespace_data.go index 482954f4e0..dee77a8bf5 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data.go +++ b/share/shwap/p2p/bitswap/row_namespace_data.go @@ -114,6 +114,9 @@ func (rndb *RowNamespaceDataBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) func (rndb *RowNamespaceDataBlock) Populate(root *share.Root) PopulateFn { return func(data []byte) error { + if !rndb.IsEmpty() { + return nil + } var rndBlk bitswapb.RowNamespaceDataBlock if err := rndBlk.Unmarshal(data); err != nil { return fmt.Errorf("unmarshaling RowNamespaceDataBlock: %w", err) diff --git a/share/shwap/p2p/bitswap/sample.go b/share/shwap/p2p/bitswap/sample.go index b7bc7f8245..841c53c50c 100644 --- a/share/shwap/p2p/bitswap/sample.go +++ b/share/shwap/p2p/bitswap/sample.go @@ -109,6 +109,9 @@ func (sb *SampleBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Bloc func (sb *SampleBlock) Populate(root *share.Root) PopulateFn { return func(data []byte) error { + if !sb.IsEmpty() { + return nil + } var sampleBlk bitswappb.SampleBlock if err := sampleBlk.Unmarshal(data); err != nil { return fmt.Errorf("unmarshaling SampleBlock: %w", err) From 4943855a63c153c62c3b0194b1737d9ee522f96a Mon Sep 17 00:00:00 2001 From: Wondertan Date: Thu, 30 May 2024 12:06:52 +0200 Subject: [PATCH 06/40] support for multipe populators --- share/shwap/p2p/bitswap/bitswap.go | 60 +++++++++++++++++++++++++++-- share/shwap/p2p/bitswap/hasher.go | 17 ++++---- share/shwap/p2p/bitswap/row_test.go | 8 ---- 3 files changed, 66 insertions(+), 19 deletions(-) diff --git a/share/shwap/p2p/bitswap/bitswap.go b/share/shwap/p2p/bitswap/bitswap.go index 064441ad44..787716d501 100644 --- a/share/shwap/p2p/bitswap/bitswap.go +++ b/share/shwap/p2p/bitswap/bitswap.go @@ -54,8 +54,9 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, ids cids = append(cids, id.CID()) idStr := id.String() - populators.Store(idStr, id.Populate(root)) - defer populators.Delete(idStr) + populateFn := id.Populate(root) + populators.Store(idStr, &populateFn) + defer populators.Delete(idStr, &populateFn) } // must start getting only after verifiers are registered @@ -80,4 +81,57 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, ids return nil } -var populators sync.Map +var populators populatorsMap + +type populatorEntry struct { + sync.Mutex + funcs map[*PopulateFn]struct{} +} + +func newPopulatorEntry(fn *PopulateFn) *populatorEntry { + return &populatorEntry{ + funcs: map[*PopulateFn]struct{}{fn: {}}, + } +} + +type populatorsMap struct { + // use sync.Map to minimize contention between disjoint keys + // which is the dominant case + mp sync.Map + mu sync.Mutex +} + +func (p *populatorsMap) Store(id string, fn *PopulateFn) { + val, ok := p.mp.LoadOrStore(id, newPopulatorEntry(fn)) + if !ok { + return + } + + entry := val.(*populatorEntry) + entry.Lock() + entry.funcs[fn] = struct{}{} + entry.Unlock() +} + +func (p *populatorsMap) Load(id string) (map[*PopulateFn]struct{}, bool) { + val, ok := p.mp.Load(id) + if !ok { + return nil, false + } + return val.(*populatorEntry).funcs, ok +} + +func (p *populatorsMap) Delete(id string, fn *PopulateFn) { + val, ok := p.mp.Load(id) + if !ok { + return + } + + entry := val.(*populatorEntry) + entry.Lock() + delete(entry.funcs, fn) + if len(entry.funcs) == 0 { + p.mp.Delete(id) + } + entry.Unlock() +} diff --git a/share/shwap/p2p/bitswap/hasher.go b/share/shwap/p2p/bitswap/hasher.go index 1bb85bbf49..70f630b7f3 100644 --- a/share/shwap/p2p/bitswap/hasher.go +++ b/share/shwap/p2p/bitswap/hasher.go @@ -26,24 +26,25 @@ func (h *hasher) Write(data []byte) (int, error) { // * Avoid type allocations id := data[pbOffset : h.IDSize+pbOffset] // get registered verifier and use it to check data validity - val, ok := populators.Load(string(id)) + populateFns, ok := populators.Load(string(id)) if !ok { err := fmt.Errorf("shwap/bitswap hasher: no verifier registered") log.Error(err) return 0, err } - populate := val.(PopulateFn) - err := populate(data) - if err != nil { - err = fmt.Errorf("shwap/bitswap hasher: verifying data: %w", err) - log.Error(err) - return 0, err + for populate := range populateFns { + err := (*populate)(data) + if err != nil { + err = fmt.Errorf("shwap/bitswap hasher: verifying data: %w", err) + log.Error(err) + return 0, err + } } // if correct set the id as resulting sum // it's required for the sum to match the original Block // to satisfy hash contract h.sum = id - return len(data), err + return len(data), nil } func (h *hasher) Sum([]byte) []byte { diff --git a/share/shwap/p2p/bitswap/row_test.go b/share/shwap/p2p/bitswap/row_test.go index 5d7c2d5d9c..b22e536d85 100644 --- a/share/shwap/p2p/bitswap/row_test.go +++ b/share/shwap/p2p/bitswap/row_test.go @@ -35,12 +35,4 @@ func TestRowRoundtrip_GetContainers(t *testing.T) { err = row.Container.Validate(root, row.ID.RowIndex) require.NoError(t, err) } - - // TODO: Should be part of a different test - var entries int - populators.Range(func(any, any) bool { - entries++ - return true - }) - require.Zero(t, entries) } From 108f4502db2b97cdcba51ac3a9b9efe03e78f83c Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 30 May 2024 15:48:01 +0500 Subject: [PATCH 07/40] remove populatorsMap and fix cid mapping for blocks --- share/shwap/p2p/bitswap/bitswap.go | 73 ++++++------------------------ share/shwap/p2p/bitswap/hasher.go | 17 ++++--- 2 files changed, 22 insertions(+), 68 deletions(-) diff --git a/share/shwap/p2p/bitswap/bitswap.go b/share/shwap/p2p/bitswap/bitswap.go index 787716d501..6d863c7970 100644 --- a/share/shwap/p2p/bitswap/bitswap.go +++ b/share/shwap/p2p/bitswap/bitswap.go @@ -47,16 +47,22 @@ type Block interface { // cause issues. TODO: Describe motivation func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, ids ...Block) error { cids := make([]cid.Cid, 0, len(ids)) + missingIds := make(map[cid.Cid]Block) for _, id := range ids { if !id.IsEmpty() { continue } - cids = append(cids, id.CID()) + cid := id.CID() + cids = append(cids, cid) idStr := id.String() - populateFn := id.Populate(root) - populators.Store(idStr, &populateFn) - defer populators.Delete(idStr, &populateFn) + p := id.Populate(root) + _, exists := populators.LoadOrStore(idStr, p) + if exists { + missingIds[cid] = id + } else { + defer populators.Delete(idStr) + } } // must start getting only after verifiers are registered @@ -68,7 +74,9 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, ids // GetBlocks handles ctx and closes blkCh, so we don't have to var amount int for blk := range blkCh { - ids[amount].Populate(root)(blk.RawData()) + if id, ok := missingIds[blk.Cid()]; ok { + id.Populate(root)(blk.RawData()) + } amount++ } if amount != len(cids) { @@ -81,57 +89,4 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, ids return nil } -var populators populatorsMap - -type populatorEntry struct { - sync.Mutex - funcs map[*PopulateFn]struct{} -} - -func newPopulatorEntry(fn *PopulateFn) *populatorEntry { - return &populatorEntry{ - funcs: map[*PopulateFn]struct{}{fn: {}}, - } -} - -type populatorsMap struct { - // use sync.Map to minimize contention between disjoint keys - // which is the dominant case - mp sync.Map - mu sync.Mutex -} - -func (p *populatorsMap) Store(id string, fn *PopulateFn) { - val, ok := p.mp.LoadOrStore(id, newPopulatorEntry(fn)) - if !ok { - return - } - - entry := val.(*populatorEntry) - entry.Lock() - entry.funcs[fn] = struct{}{} - entry.Unlock() -} - -func (p *populatorsMap) Load(id string) (map[*PopulateFn]struct{}, bool) { - val, ok := p.mp.Load(id) - if !ok { - return nil, false - } - return val.(*populatorEntry).funcs, ok -} - -func (p *populatorsMap) Delete(id string, fn *PopulateFn) { - val, ok := p.mp.Load(id) - if !ok { - return - } - - entry := val.(*populatorEntry) - entry.Lock() - delete(entry.funcs, fn) - if len(entry.funcs) == 0 { - p.mp.Delete(id) - } - entry.Unlock() -} +var populators sync.Map diff --git a/share/shwap/p2p/bitswap/hasher.go b/share/shwap/p2p/bitswap/hasher.go index 70f630b7f3..1bb85bbf49 100644 --- a/share/shwap/p2p/bitswap/hasher.go +++ b/share/shwap/p2p/bitswap/hasher.go @@ -26,25 +26,24 @@ func (h *hasher) Write(data []byte) (int, error) { // * Avoid type allocations id := data[pbOffset : h.IDSize+pbOffset] // get registered verifier and use it to check data validity - populateFns, ok := populators.Load(string(id)) + val, ok := populators.Load(string(id)) if !ok { err := fmt.Errorf("shwap/bitswap hasher: no verifier registered") log.Error(err) return 0, err } - for populate := range populateFns { - err := (*populate)(data) - if err != nil { - err = fmt.Errorf("shwap/bitswap hasher: verifying data: %w", err) - log.Error(err) - return 0, err - } + populate := val.(PopulateFn) + err := populate(data) + if err != nil { + err = fmt.Errorf("shwap/bitswap hasher: verifying data: %w", err) + log.Error(err) + return 0, err } // if correct set the id as resulting sum // it's required for the sum to match the original Block // to satisfy hash contract h.sum = id - return len(data), nil + return len(data), err } func (h *hasher) Sum([]byte) []byte { From 60b3a25a71df3ddadbd540f6f87a3d68663ccb39 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Thu, 30 May 2024 22:38:10 +0200 Subject: [PATCH 08/40] beutify --- share/shwap/p2p/bitswap/bitswap.go | 92 ----------- share/shwap/p2p/bitswap/block.go | 43 ++++++ share/shwap/p2p/bitswap/block_fetch.go | 146 ++++++++++++++++++ .../{bitswap_test.go => block_fetch_test.go} | 14 +- share/shwap/p2p/bitswap/block_registry.go | 29 ++++ share/shwap/p2p/bitswap/cid.go | 6 +- share/shwap/p2p/bitswap/hasher.go | 63 -------- share/shwap/p2p/bitswap/hasher_test.go | 1 - share/shwap/p2p/bitswap/registry.go | 36 ----- .../p2p/bitswap/{row.go => row_block.go} | 14 +- .../{row_test.go => row_block_test.go} | 2 +- ...ce_data.go => row_namespace_data_block.go} | 14 +- ...st.go => row_namespace_data_block_test.go} | 2 +- .../bitswap/{sample.go => sample_block.go} | 14 +- .../{sample_test.go => sample_block_test.go} | 2 +- 15 files changed, 255 insertions(+), 223 deletions(-) delete mode 100644 share/shwap/p2p/bitswap/bitswap.go create mode 100644 share/shwap/p2p/bitswap/block.go create mode 100644 share/shwap/p2p/bitswap/block_fetch.go rename share/shwap/p2p/bitswap/{bitswap_test.go => block_fetch_test.go} (91%) create mode 100644 share/shwap/p2p/bitswap/block_registry.go delete mode 100644 share/shwap/p2p/bitswap/hasher.go delete mode 100644 share/shwap/p2p/bitswap/hasher_test.go delete mode 100644 share/shwap/p2p/bitswap/registry.go rename share/shwap/p2p/bitswap/{row.go => row_block.go} (92%) rename share/shwap/p2p/bitswap/{row_test.go => row_block_test.go} (93%) rename share/shwap/p2p/bitswap/{row_namespace_data.go => row_namespace_data_block.go} (91%) rename share/shwap/p2p/bitswap/{row_namespace_data_test.go => row_namespace_data_block_test.go} (94%) rename share/shwap/p2p/bitswap/{sample.go => sample_block.go} (92%) rename share/shwap/p2p/bitswap/{sample_test.go => sample_block_test.go} (94%) diff --git a/share/shwap/p2p/bitswap/bitswap.go b/share/shwap/p2p/bitswap/bitswap.go deleted file mode 100644 index 6d863c7970..0000000000 --- a/share/shwap/p2p/bitswap/bitswap.go +++ /dev/null @@ -1,92 +0,0 @@ -package bitswap - -import ( - "context" - "fmt" - "sync" - - "github.com/ipfs/boxo/exchange" - "github.com/ipfs/go-cid" - logger "github.com/ipfs/go-log/v2" - - "github.com/celestiaorg/celestia-node/share" -) - -var log = logger.Logger("shwap/bitswap") - -// TODO: -// * Synchronization for Fetch -// * Test with race and count 100 -// * Hasher test -// * Coverage -// * godoc -// * document steps required to add new id/container type - -// PopulateFn is a closure that validates given bytes and populates -// Blocks with serialized shwap container in those bytes on success. -type PopulateFn func([]byte) error - -type Block interface { - blockBuilder - - // String returns string representation of the Block - // to be used as map key. Might not be human-readable - String() string - // CID returns shwap ID of the Block formatted as CID. - CID() cid.Cid - // IsEmpty reports whether the Block has the shwap container. - // If the Block is empty, it can be populated with Fetch. - IsEmpty() bool - // Populate returns closure that fills up the Block with shwap container. - // Population involves data validation against the Root. - Populate(*share.Root) PopulateFn -} - -// Fetch -// Does not guarantee synchronization. Calling this func simultaneously with the same Block may -// cause issues. TODO: Describe motivation -func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, ids ...Block) error { - cids := make([]cid.Cid, 0, len(ids)) - missingIds := make(map[cid.Cid]Block) - for _, id := range ids { - if !id.IsEmpty() { - continue - } - cid := id.CID() - cids = append(cids, cid) - - idStr := id.String() - p := id.Populate(root) - _, exists := populators.LoadOrStore(idStr, p) - if exists { - missingIds[cid] = id - } else { - defer populators.Delete(idStr) - } - } - - // must start getting only after verifiers are registered - blkCh, err := fetcher.GetBlocks(ctx, cids) - if err != nil { - return fmt.Errorf("fetching bitswap blocks: %w", err) - } - - // GetBlocks handles ctx and closes blkCh, so we don't have to - var amount int - for blk := range blkCh { - if id, ok := missingIds[blk.Cid()]; ok { - id.Populate(root)(blk.RawData()) - } - amount++ - } - if amount != len(cids) { - if ctx.Err() != nil { - return ctx.Err() - } - return fmt.Errorf("not all the containers were found") - } - - return nil -} - -var populators sync.Map diff --git a/share/shwap/p2p/bitswap/block.go b/share/shwap/p2p/bitswap/block.go new file mode 100644 index 0000000000..21fbfd643a --- /dev/null +++ b/share/shwap/p2p/bitswap/block.go @@ -0,0 +1,43 @@ +package bitswap + +import ( + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + logger "github.com/ipfs/go-log/v2" + + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" +) + +var log = logger.Logger("shwap/bitswap") + +// TODO: +// * Coverage +// * godoc +// * document steps required to add new id/container type + +// PopulateFn is a closure produced by a Block that validates given +// serialized Shwap container and populates the Block with it on success. +type PopulateFn func([]byte) error + +// Block represents Bitswap compatible Shwap container. +// All Shwap containers must have a RegisterBlock-ed wrapper +// implementing the interface to be compatible with Bitswap. +type Block interface { + // String returns string representation of the Block + // to be used as map key. Might not be human-readable. + String() string + + // CID returns Shwap ID of the Block formatted as CID. + CID() cid.Cid + // BlockFromEDS extract Bitswap Block out of the EDS. + BlockFromEDS(*rsmt2d.ExtendedDataSquare) (blocks.Block, error) + + // IsEmpty reports whether the Block been populated with Shwap container. + // If the Block is empty, it can be populated with Fetch. + IsEmpty() bool + // Populate returns closure that fills up the Block with Shwap container. + // Population involves data validation against the Root. + Populate(*share.Root) PopulateFn +} diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go new file mode 100644 index 0000000000..5fc90b8a23 --- /dev/null +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -0,0 +1,146 @@ +package bitswap + +import ( + "context" + "crypto/sha256" + "fmt" + "sync" + + "github.com/ipfs/boxo/exchange" + "github.com/ipfs/go-cid" + + "github.com/celestiaorg/celestia-node/share" +) + +// Fetch fetches and populates given Blocks using Fetcher wrapping Bitswap. +// +// Validates Block against the given Root and skips Blocks that are already populated. +// Gracefully synchronize identical Blocks requested simultaneously. +// Blocks until either context is canceled or all Blocks are fetched and populated. +func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks ...Block) error { + cids := make([]cid.Cid, 0, len(blks)) + duplicate := make(map[cid.Cid]Block) + for _, blk := range blks { + if !blk.IsEmpty() { + continue // skip populated Blocks + } + + idStr := blk.String() + p := blk.Populate(root) + cid := blk.CID() + cids = append(cids, cid) + // store the PopulateFn s.t. hasher can access it + // and fill in the Block + _, exists := populators.LoadOrStore(idStr, p) + if exists { + // in case there is ongoing fetch happening for the same Block elsewhere + // and PopulateFn has already been set -- mark the Block as duplicate + duplicate[cid] = blk + } else { + // only do the cleanup if we stored the PopulateFn + defer populators.Delete(idStr) + } + } + + blkCh, err := fetcher.GetBlocks(ctx, cids) + if err != nil { + return fmt.Errorf("fetching bitswap blocks: %w", err) + } + + for blk := range blkCh { + if ctx.Err() != nil { // GetBlocks closes blkCh on ctx cancellation + return ctx.Err() + } + // check if the blk is a duplicate + id, ok := duplicate[blk.Cid()] + if !ok { + continue + } + // if it is, we have to populate it ourselves instead of hasher, + // as there is only one PopulateFN allowed per ID + err := id.Populate(root)(blk.RawData()) + if err != nil { + // this case should never happen in practice + // and if so something is really wrong + panic(fmt.Sprintf("populating duplicate block: %s", err)) + } + // NOTE: This approach has a downside that we redo deserialization and computationally + // expensive computation for as many duplicates. We tried solutions that doesn't have this + // problem, but they are *much* more complex. Considering this a rare edge-case the tradeoff + // towards simplicity has been made. + } + + return nil +} + +// populators exists to communicate between Fetch and hasher. +// +// Fetch registers PopulateFNs that hasher then uses to validate and populate Block responses coming +// through Bitswap +// +// Bitswap does not provide *stateful* verification out of the box and by default +// messages are verified by their respective MultiHashes that are registered globally. +// For every Block type there is a global hasher registered that accesses stored PopulateFn once a +// message is received. It then uses PopulateFn to validate and fill in the respective Block +// +// sync.Map is used to minimize contention for disjoint keys +var populators sync.Map + +// hasher implements hash.Hash to be registered as custom multihash +// hasher is the *hack* to inject custom verification logic into Bitswap +type hasher struct { + // IDSize of the respective Shwap container + IDSize int // to be set during hasher registration + + sum []byte +} + +func (h *hasher) Write(data []byte) (int, error) { + const pbOffset = 2 // this assumes the protobuf serialization is in use + if len(data) < h.IDSize+pbOffset { + err := fmt.Errorf("shwap/bitswap hasher: insufficient data size") + log.Error() + return 0, err + } + // extract Block out of data + // we do this on the raw data to: + // * Avoid complicating hasher with generalized bytes -> type unmarshalling + // * Avoid type allocations + id := data[pbOffset : h.IDSize+pbOffset] + // get registered PopulateFn and use it to check data validity and + // pass it to Fetch caller + val, ok := populators.Load(string(id)) + if !ok { + err := fmt.Errorf("shwap/bitswap hasher: no verifier registered") + log.Error(err) + return 0, err + } + populate := val.(PopulateFn) + err := populate(data) + if err != nil { + err = fmt.Errorf("shwap/bitswap hasher: verifying data: %w", err) + log.Error(err) + return 0, err + } + // set the id as resulting sum + // it's required for the sum to match the requested ID + // to satisfy hash contract and signal to Bitswap that data is correct + h.sum = id + return len(data), err +} + +func (h *hasher) Sum([]byte) []byte { + return h.sum +} + +func (h *hasher) Reset() { + h.sum = nil +} + +func (h *hasher) Size() int { + return h.IDSize +} + +func (h *hasher) BlockSize() int { + return sha256.BlockSize +} diff --git a/share/shwap/p2p/bitswap/bitswap_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go similarity index 91% rename from share/shwap/p2p/bitswap/bitswap_test.go rename to share/shwap/p2p/bitswap/block_fetch_test.go index df92665aeb..985b834025 100644 --- a/share/shwap/p2p/bitswap/bitswap_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -24,27 +24,25 @@ import ( "github.com/celestiaorg/rsmt2d" - "github.com/celestiaorg/celestia-node/logs" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/edstest" ) func TestFetchDuplicates(t *testing.T) { - logs.SetDebugLogging() ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() eds := edstest.RandEDS(t, 4) root, err := share.NewRoot(eds) require.NoError(t, err) - client := remoteClient(ctx, t, newTestBlockstore(eds)) + client := fetcher(ctx, t, newTestBlockstore(eds)) var wg sync.WaitGroup - var blks []*RowBlock - for range 100 { + blks := make([]*RowBlock, 100) + for i := range blks { blk, err := NewEmptyRowBlock(1, 0, root) // create the same Block ID require.NoError(t, err) - blks = append(blks, blk) + blks[i] = blk wg.Add(1) go func() { @@ -56,12 +54,10 @@ func TestFetchDuplicates(t *testing.T) { assert.NoError(t, err) wg.Done() }() - } wg.Wait() for _, blk := range blks { - t.Log(blk.IsEmpty()) assert.False(t, blk.IsEmpty()) } @@ -73,7 +69,7 @@ func TestFetchDuplicates(t *testing.T) { require.Zero(t, entries) } -func remoteClient(ctx context.Context, t *testing.T, bstore blockstore.Blockstore) exchange.Fetcher { +func fetcher(ctx context.Context, t *testing.T, bstore blockstore.Blockstore) exchange.Fetcher { net, err := mocknet.FullMeshLinked(2) require.NoError(t, err) diff --git a/share/shwap/p2p/bitswap/block_registry.go b/share/shwap/p2p/bitswap/block_registry.go new file mode 100644 index 0000000000..625d0870c1 --- /dev/null +++ b/share/shwap/p2p/bitswap/block_registry.go @@ -0,0 +1,29 @@ +package bitswap + +import ( + "hash" + + "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" +) + +// RegisterBlock registers the new Block type and multihash for it. +func RegisterBlock(mhcode, codec uint64, size int, bldrFn func(cid.Cid) (Block, error)) { + mh.Register(mhcode, func() hash.Hash { + return &hasher{IDSize: size} + }) + specRegistry[mhcode] = blockSpec{ + size: size, + codec: codec, + builder: bldrFn, + } +} + +// blockSpec holds +type blockSpec struct { + size int + codec uint64 + builder func(cid.Cid) (Block, error) +} + +var specRegistry = make(map[uint64]blockSpec) diff --git a/share/shwap/p2p/bitswap/cid.go b/share/shwap/p2p/bitswap/cid.go index a758dc9cfa..53129393da 100644 --- a/share/shwap/p2p/bitswap/cid.go +++ b/share/shwap/p2p/bitswap/cid.go @@ -10,7 +10,8 @@ import ( // DefaultAllowlist keeps default list of multihashes allowed in the network. // TODO(@Wondertan): Make it private and instead provide Blockservice constructor with injected -// allowlist +// +// allowlist var DefaultAllowlist allowlist type allowlist struct{} @@ -21,6 +22,7 @@ func (a allowlist) IsAllowed(code uint64) bool { return ok } +// extractCID retrieves Shwap ID out of the CID. func extractCID(cid cid.Cid) ([]byte, error) { if err := validateCID(cid); err != nil { return nil, err @@ -30,6 +32,7 @@ func extractCID(cid cid.Cid) ([]byte, error) { return cid.Hash()[mhPrefixSize:], nil } +// encodeCID encodes Shwap ID into the CID. func encodeCID(bm encoding.BinaryMarshaler, mhcode, codec uint64) cid.Cid { data, err := bm.MarshalBinary() if err != nil { @@ -44,6 +47,7 @@ func encodeCID(bm encoding.BinaryMarshaler, mhcode, codec uint64) cid.Cid { return cid.NewCidV1(codec, buf) } +// validateCID checks correctness of the CID. func validateCID(cid cid.Cid) error { prefix := cid.Prefix() spec, ok := specRegistry[prefix.MhType] diff --git a/share/shwap/p2p/bitswap/hasher.go b/share/shwap/p2p/bitswap/hasher.go deleted file mode 100644 index 1bb85bbf49..0000000000 --- a/share/shwap/p2p/bitswap/hasher.go +++ /dev/null @@ -1,63 +0,0 @@ -package bitswap - -import ( - "crypto/sha256" - "fmt" -) - -// hasher -// TODO: Describe the Hack this all is -type hasher struct { - IDSize int - - sum []byte -} - -func (h *hasher) Write(data []byte) (int, error) { - const pbOffset = 2 // this assumes the protobuf serialization is in use - if len(data) < h.IDSize+pbOffset { - err := fmt.Errorf("shwap/bitswap hasher: insufficient data size") - log.Error() - return 0, err - } - // extract Block out of data - // we do this on the raw data to: - // * Avoid complicating hasher with generalized bytes -> type unmarshalling - // * Avoid type allocations - id := data[pbOffset : h.IDSize+pbOffset] - // get registered verifier and use it to check data validity - val, ok := populators.Load(string(id)) - if !ok { - err := fmt.Errorf("shwap/bitswap hasher: no verifier registered") - log.Error(err) - return 0, err - } - populate := val.(PopulateFn) - err := populate(data) - if err != nil { - err = fmt.Errorf("shwap/bitswap hasher: verifying data: %w", err) - log.Error(err) - return 0, err - } - // if correct set the id as resulting sum - // it's required for the sum to match the original Block - // to satisfy hash contract - h.sum = id - return len(data), err -} - -func (h *hasher) Sum([]byte) []byte { - return h.sum -} - -func (h *hasher) Reset() { - h.sum = nil -} - -func (h *hasher) Size() int { - return h.IDSize -} - -func (h *hasher) BlockSize() int { - return sha256.BlockSize -} diff --git a/share/shwap/p2p/bitswap/hasher_test.go b/share/shwap/p2p/bitswap/hasher_test.go deleted file mode 100644 index 67a03afb70..0000000000 --- a/share/shwap/p2p/bitswap/hasher_test.go +++ /dev/null @@ -1 +0,0 @@ -package bitswap diff --git a/share/shwap/p2p/bitswap/registry.go b/share/shwap/p2p/bitswap/registry.go deleted file mode 100644 index 66a2a3ff97..0000000000 --- a/share/shwap/p2p/bitswap/registry.go +++ /dev/null @@ -1,36 +0,0 @@ -package bitswap - -import ( - "hash" - - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" - mh "github.com/multiformats/go-multihash" - - "github.com/celestiaorg/rsmt2d" -) - -// RegisterBlock registers the new Block type. -func RegisterBlock(mhcode, codec uint64, size int, bldrFn func(cid.Cid) (blockBuilder, error)) { - mh.Register(mhcode, func() hash.Hash { - return &hasher{IDSize: size} - }) - specRegistry[mhcode] = idSpec{ - size: size, - codec: codec, - builder: bldrFn, - } -} - -var specRegistry = make(map[uint64]idSpec) - -type idSpec struct { - size int - codec uint64 - builder func(cid.Cid) (blockBuilder, error) -} - -type blockBuilder interface { - // BlockFromEDS gets Bitswap Block out of the EDS. - BlockFromEDS(*rsmt2d.ExtendedDataSquare) (blocks.Block, error) -} diff --git a/share/shwap/p2p/bitswap/row.go b/share/shwap/p2p/bitswap/row_block.go similarity index 92% rename from share/shwap/p2p/bitswap/row.go rename to share/shwap/p2p/bitswap/row_block.go index 9fa232d41c..b7ff9e9982 100644 --- a/share/shwap/p2p/bitswap/row.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -26,17 +26,19 @@ func init() { rowMultihashCode, rowCodec, shwap.RowIDSize, - func(cid cid.Cid) (blockBuilder, error) { + func(cid cid.Cid) (Block, error) { return EmptyRowBlockFromCID(cid) }, ) } +// RowBlock is a Bitswap compatible block for Shwap's Row container. type RowBlock struct { ID shwap.RowID Container *shwap.Row } +// NewEmptyRowBlock constructs a new empty RowBlock. func NewEmptyRowBlock(height uint64, rowIdx int, root *share.Root) (*RowBlock, error) { id, err := shwap.NewRowID(height, rowIdx, root) if err != nil { @@ -46,7 +48,7 @@ func NewEmptyRowBlock(height uint64, rowIdx int, root *share.Root) (*RowBlock, e return &RowBlock{ID: id}, nil } -// EmptyRowBlockFromCID coverts CID to RowBlock. +// EmptyRowBlockFromCID constructs an empty RowBlock out of the CID. func EmptyRowBlockFromCID(cid cid.Cid) (*RowBlock, error) { ridData, err := extractCID(cid) if err != nil { @@ -60,10 +62,6 @@ func EmptyRowBlockFromCID(cid cid.Cid) (*RowBlock, error) { return &RowBlock{ID: rid}, nil } -func (rb *RowBlock) IsEmpty() bool { - return rb.Container == nil -} - func (rb *RowBlock) String() string { data, err := rb.ID.MarshalBinary() if err != nil { @@ -102,6 +100,10 @@ func (rb *RowBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, return blk, nil } +func (rb *RowBlock) IsEmpty() bool { + return rb.Container == nil +} + func (rb *RowBlock) Populate(root *share.Root) PopulateFn { return func(data []byte) error { if !rb.IsEmpty() { diff --git a/share/shwap/p2p/bitswap/row_test.go b/share/shwap/p2p/bitswap/row_block_test.go similarity index 93% rename from share/shwap/p2p/bitswap/row_test.go rename to share/shwap/p2p/bitswap/row_block_test.go index b22e536d85..e2432955cd 100644 --- a/share/shwap/p2p/bitswap/row_test.go +++ b/share/shwap/p2p/bitswap/row_block_test.go @@ -18,7 +18,7 @@ func TestRowRoundtrip_GetContainers(t *testing.T) { eds := edstest.RandEDS(t, 4) root, err := share.NewRoot(eds) require.NoError(t, err) - client := remoteClient(ctx, t, newTestBlockstore(eds)) + client := fetcher(ctx, t, newTestBlockstore(eds)) blks := make([]Block, eds.Width()) for i := range blks { diff --git a/share/shwap/p2p/bitswap/row_namespace_data.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go similarity index 91% rename from share/shwap/p2p/bitswap/row_namespace_data.go rename to share/shwap/p2p/bitswap/row_namespace_data_block.go index dee77a8bf5..d1e93443b2 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -26,17 +26,19 @@ func init() { rowNamespaceDataMultihashCode, rowNamespaceDataCodec, shwap.RowNamespaceDataIDSize, - func(cid cid.Cid) (blockBuilder, error) { + func(cid cid.Cid) (Block, error) { return EmptyRowNamespaceDataBlockFromCID(cid) }, ) } +// RowNamespaceDataBlock is a Bitswap compatible block for Shwap's RowNamespaceData container. type RowNamespaceDataBlock struct { ID shwap.RowNamespaceDataID Container *shwap.RowNamespaceData } +// NewEmptyRowNamespaceDataBlock constructs a new empty RowNamespaceDataBlock. func NewEmptyRowNamespaceDataBlock( height uint64, rowIdx int, @@ -51,7 +53,7 @@ func NewEmptyRowNamespaceDataBlock( return &RowNamespaceDataBlock{ID: id}, nil } -// EmptyRowNamespaceDataBlockFromCID coverts CID to RowNamespaceDataBlock. +// EmptyRowNamespaceDataBlockFromCID constructs an empty RowNamespaceDataBlock out of the CID. func EmptyRowNamespaceDataBlockFromCID(cid cid.Cid) (*RowNamespaceDataBlock, error) { rndidData, err := extractCID(cid) if err != nil { @@ -66,10 +68,6 @@ func EmptyRowNamespaceDataBlockFromCID(cid cid.Cid) (*RowNamespaceDataBlock, err return &RowNamespaceDataBlock{ID: rndid}, nil } -func (rndb *RowNamespaceDataBlock) IsEmpty() bool { - return rndb.Container == nil -} - func (rndb *RowNamespaceDataBlock) String() string { data, err := rndb.ID.MarshalBinary() if err != nil { @@ -112,6 +110,10 @@ func (rndb *RowNamespaceDataBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) return blk, nil } +func (rndb *RowNamespaceDataBlock) IsEmpty() bool { + return rndb.Container == nil +} + func (rndb *RowNamespaceDataBlock) Populate(root *share.Root) PopulateFn { return func(data []byte) error { if !rndb.IsEmpty() { diff --git a/share/shwap/p2p/bitswap/row_namespace_data_test.go b/share/shwap/p2p/bitswap/row_namespace_data_block_test.go similarity index 94% rename from share/shwap/p2p/bitswap/row_namespace_data_test.go rename to share/shwap/p2p/bitswap/row_namespace_data_block_test.go index bf5a7259a3..1d1f96b0ff 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_test.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block_test.go @@ -18,7 +18,7 @@ func TestRowNamespaceDataRoundtrip_GetContainers(t *testing.T) { namespace := sharetest.RandV0Namespace() eds, root := edstest.RandEDSWithNamespace(t, namespace, 64, 16) - client := remoteClient(ctx, t, newTestBlockstore(eds)) + client := fetcher(ctx, t, newTestBlockstore(eds)) rowIdxs := share.RowsWithNamespace(root, namespace) blks := make([]Block, len(rowIdxs)) diff --git a/share/shwap/p2p/bitswap/sample.go b/share/shwap/p2p/bitswap/sample_block.go similarity index 92% rename from share/shwap/p2p/bitswap/sample.go rename to share/shwap/p2p/bitswap/sample_block.go index 841c53c50c..8d70b4a66b 100644 --- a/share/shwap/p2p/bitswap/sample.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -27,17 +27,19 @@ func init() { sampleMultihashCode, sampleCodec, shwap.SampleIDSize, - func(cid cid.Cid) (blockBuilder, error) { + func(cid cid.Cid) (Block, error) { return EmptySampleBlockFromCID(cid) }, ) } +// SampleBlock is a Bitswap compatible block for Shwap's Sample container. type SampleBlock struct { ID shwap.SampleID Container *shwap.Sample } +// NewEmptySampleBlock constructs a new empty SampleBlock. func NewEmptySampleBlock(height uint64, rowIdx, colIdx int, root *share.Root) (*SampleBlock, error) { id, err := shwap.NewSampleID(height, rowIdx, colIdx, root) if err != nil { @@ -47,7 +49,7 @@ func NewEmptySampleBlock(height uint64, rowIdx, colIdx int, root *share.Root) (* return &SampleBlock{ID: id}, nil } -// EmptySampleBlockFromCID coverts CID to SampleBlock. +// EmptySampleBlockFromCID constructs an empty SampleBlock out of the CID. func EmptySampleBlockFromCID(cid cid.Cid) (*SampleBlock, error) { sidData, err := extractCID(cid) if err != nil { @@ -62,10 +64,6 @@ func EmptySampleBlockFromCID(cid cid.Cid) (*SampleBlock, error) { return &SampleBlock{ID: sid}, nil } -func (sb *SampleBlock) IsEmpty() bool { - return sb.Container == nil -} - func (sb *SampleBlock) String() string { data, err := sb.ID.MarshalBinary() if err != nil { @@ -107,6 +105,10 @@ func (sb *SampleBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Bloc return blk, nil } +func (sb *SampleBlock) IsEmpty() bool { + return sb.Container == nil +} + func (sb *SampleBlock) Populate(root *share.Root) PopulateFn { return func(data []byte) error { if !sb.IsEmpty() { diff --git a/share/shwap/p2p/bitswap/sample_test.go b/share/shwap/p2p/bitswap/sample_block_test.go similarity index 94% rename from share/shwap/p2p/bitswap/sample_test.go rename to share/shwap/p2p/bitswap/sample_block_test.go index 6e04eac4d7..81dd957769 100644 --- a/share/shwap/p2p/bitswap/sample_test.go +++ b/share/shwap/p2p/bitswap/sample_block_test.go @@ -18,7 +18,7 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { eds := edstest.RandEDS(t, 8) root, err := share.NewRoot(eds) require.NoError(t, err) - client := remoteClient(ctx, t, newTestBlockstore(eds)) + client := fetcher(ctx, t, newTestBlockstore(eds)) width := int(eds.Width()) blks := make([]Block, 0, width*width) From a44b8b320a4f4c9ec41e5f742db2f0c3250848c2 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Thu, 30 May 2024 23:12:11 +0200 Subject: [PATCH 09/40] fix context --- share/shwap/p2p/bitswap/block.go | 5 ----- share/shwap/p2p/bitswap/block_fetch.go | 7 ++----- share/shwap/p2p/bitswap/block_fetch_test.go | 4 ++-- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/share/shwap/p2p/bitswap/block.go b/share/shwap/p2p/bitswap/block.go index 21fbfd643a..7f562cb391 100644 --- a/share/shwap/p2p/bitswap/block.go +++ b/share/shwap/p2p/bitswap/block.go @@ -12,11 +12,6 @@ import ( var log = logger.Logger("shwap/bitswap") -// TODO: -// * Coverage -// * godoc -// * document steps required to add new id/container type - // PopulateFn is a closure produced by a Block that validates given // serialized Shwap container and populates the Block with it on success. type PopulateFn func([]byte) error diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 5fc90b8a23..cfd2b2f558 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -47,10 +47,7 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks return fmt.Errorf("fetching bitswap blocks: %w", err) } - for blk := range blkCh { - if ctx.Err() != nil { // GetBlocks closes blkCh on ctx cancellation - return ctx.Err() - } + for blk := range blkCh { // GetBlocks closes blkCh on ctx cancellation // check if the blk is a duplicate id, ok := duplicate[blk.Cid()] if !ok { @@ -70,7 +67,7 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks // towards simplicity has been made. } - return nil + return ctx.Err() } // populators exists to communicate between Fetch and hasher. diff --git a/share/shwap/p2p/bitswap/block_fetch_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go index 985b834025..174a88733b 100644 --- a/share/shwap/p2p/bitswap/block_fetch_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -35,7 +35,7 @@ func TestFetchDuplicates(t *testing.T) { eds := edstest.RandEDS(t, 4) root, err := share.NewRoot(eds) require.NoError(t, err) - client := fetcher(ctx, t, newTestBlockstore(eds)) + fetcher := fetcher(ctx, t, newTestBlockstore(eds)) var wg sync.WaitGroup blks := make([]*RowBlock, 100) @@ -50,7 +50,7 @@ func TestFetchDuplicates(t *testing.T) { // this sleep ensures fetches aren't started simultaneously allowing to check for edge-cases time.Sleep(time.Millisecond * time.Duration(rint)) - err := Fetch(ctx, client, root, blk) + err := Fetch(ctx, fetcher, root, blk) assert.NoError(t, err) wg.Done() }() From 94b85196c29bac389f6b84914e8012539d4857da Mon Sep 17 00:00:00 2001 From: Wondertan Date: Thu, 30 May 2024 23:40:14 +0200 Subject: [PATCH 10/40] abort irrelevant change --- share/shwap/sample_id.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/shwap/sample_id.go b/share/shwap/sample_id.go index 0febc53ae7..33e83fe12d 100644 --- a/share/shwap/sample_id.go +++ b/share/shwap/sample_id.go @@ -44,7 +44,7 @@ func NewSampleID(height uint64, rowIdx, colIdx int, root *share.Root) (SampleID, // the expected size. func SampleIDFromBinary(data []byte) (SampleID, error) { if len(data) != SampleIDSize { - return SampleID{}, fmt.Errorf("invalid SampleBlock data length: expected %d, got %d", SampleIDSize, len(data)) + return SampleID{}, fmt.Errorf("invalid SampleID data length: expected %d, got %d", SampleIDSize, len(data)) } rid, err := RowIDFromBinary(data[:RowIDSize]) From 4ad1ac7144d98a27759369ec8469ca7b9ec42315 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Tue, 4 Jun 2024 22:12:39 +0200 Subject: [PATCH 11/40] comment and API fixes --- share/shwap/p2p/bitswap/block.go | 6 +++--- share/shwap/p2p/bitswap/block_fetch.go | 14 +++++++------- share/shwap/p2p/bitswap/block_fetch_test.go | 2 +- share/shwap/p2p/bitswap/block_registry.go | 4 ++-- share/shwap/p2p/bitswap/row_block.go | 12 ++++++------ .../p2p/bitswap/row_namespace_data_block.go | 16 ++++++++-------- share/shwap/p2p/bitswap/sample_block.go | 13 +++++++------ 7 files changed, 34 insertions(+), 33 deletions(-) diff --git a/share/shwap/p2p/bitswap/block.go b/share/shwap/p2p/bitswap/block.go index 7f562cb391..91ed76c8f9 100644 --- a/share/shwap/p2p/bitswap/block.go +++ b/share/shwap/p2p/bitswap/block.go @@ -17,7 +17,7 @@ var log = logger.Logger("shwap/bitswap") type PopulateFn func([]byte) error // Block represents Bitswap compatible Shwap container. -// All Shwap containers must have a RegisterBlock-ed wrapper +// All Shwap containers must have a registerBlock-ed wrapper // implementing the interface to be compatible with Bitswap. type Block interface { // String returns string representation of the Block @@ -32,7 +32,7 @@ type Block interface { // IsEmpty reports whether the Block been populated with Shwap container. // If the Block is empty, it can be populated with Fetch. IsEmpty() bool - // Populate returns closure that fills up the Block with Shwap container. + // PopulateFn returns closure that fills up the Block with Shwap container. // Population involves data validation against the Root. - Populate(*share.Root) PopulateFn + PopulateFn(*share.Root) PopulateFn } diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index cfd2b2f558..3390b9c450 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -26,19 +26,19 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks } idStr := blk.String() - p := blk.Populate(root) + p := blk.PopulateFn(root) cid := blk.CID() cids = append(cids, cid) // store the PopulateFn s.t. hasher can access it // and fill in the Block - _, exists := populators.LoadOrStore(idStr, p) + _, exists := populatorFns.LoadOrStore(idStr, p) if exists { // in case there is ongoing fetch happening for the same Block elsewhere // and PopulateFn has already been set -- mark the Block as duplicate duplicate[cid] = blk } else { // only do the cleanup if we stored the PopulateFn - defer populators.Delete(idStr) + defer populatorFns.Delete(idStr) } } @@ -55,7 +55,7 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks } // if it is, we have to populate it ourselves instead of hasher, // as there is only one PopulateFN allowed per ID - err := id.Populate(root)(blk.RawData()) + err := id.PopulateFn(root)(blk.RawData()) if err != nil { // this case should never happen in practice // and if so something is really wrong @@ -70,7 +70,7 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks return ctx.Err() } -// populators exists to communicate between Fetch and hasher. +// populatorFns exist to communicate between Fetch and hasher. // // Fetch registers PopulateFNs that hasher then uses to validate and populate Block responses coming // through Bitswap @@ -81,7 +81,7 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks // message is received. It then uses PopulateFn to validate and fill in the respective Block // // sync.Map is used to minimize contention for disjoint keys -var populators sync.Map +var populatorFns sync.Map // hasher implements hash.Hash to be registered as custom multihash // hasher is the *hack* to inject custom verification logic into Bitswap @@ -106,7 +106,7 @@ func (h *hasher) Write(data []byte) (int, error) { id := data[pbOffset : h.IDSize+pbOffset] // get registered PopulateFn and use it to check data validity and // pass it to Fetch caller - val, ok := populators.Load(string(id)) + val, ok := populatorFns.Load(string(id)) if !ok { err := fmt.Errorf("shwap/bitswap hasher: no verifier registered") log.Error(err) diff --git a/share/shwap/p2p/bitswap/block_fetch_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go index 174a88733b..82463ead0d 100644 --- a/share/shwap/p2p/bitswap/block_fetch_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -62,7 +62,7 @@ func TestFetchDuplicates(t *testing.T) { } var entries int - populators.Range(func(any, any) bool { + populatorFns.Range(func(any, any) bool { entries++ return true }) diff --git a/share/shwap/p2p/bitswap/block_registry.go b/share/shwap/p2p/bitswap/block_registry.go index 625d0870c1..0b0d7bd1f4 100644 --- a/share/shwap/p2p/bitswap/block_registry.go +++ b/share/shwap/p2p/bitswap/block_registry.go @@ -7,8 +7,8 @@ import ( mh "github.com/multiformats/go-multihash" ) -// RegisterBlock registers the new Block type and multihash for it. -func RegisterBlock(mhcode, codec uint64, size int, bldrFn func(cid.Cid) (Block, error)) { +// registerBlock registers the new Block type and multihash for it. +func registerBlock(mhcode, codec uint64, size int, bldrFn func(cid.Cid) (Block, error)) { mh.Register(mhcode, func() hash.Hash { return &hasher{IDSize: size} }) diff --git a/share/shwap/p2p/bitswap/row_block.go b/share/shwap/p2p/bitswap/row_block.go index b7ff9e9982..86fe9c205e 100644 --- a/share/shwap/p2p/bitswap/row_block.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -22,7 +22,7 @@ const ( ) func init() { - RegisterBlock( + registerBlock( rowMultihashCode, rowCodec, shwap.RowIDSize, @@ -65,7 +65,7 @@ func EmptyRowBlockFromCID(cid cid.Cid) (*RowBlock, error) { func (rb *RowBlock) String() string { data, err := rb.ID.MarshalBinary() if err != nil { - panic(fmt.Errorf("marshaling RowBlock: %w", err)) + panic(fmt.Errorf("marshaling RowID: %w", err)) } return string(data) } @@ -79,7 +79,7 @@ func (rb *RowBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, rowID, err := rb.ID.MarshalBinary() if err != nil { - return nil, fmt.Errorf("marshaling RowBlock: %w", err) + return nil, fmt.Errorf("marshaling RowID: %w", err) } rowBlk := bitswappb.RowBlock{ @@ -94,7 +94,7 @@ func (rb *RowBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, blk, err := blocks.NewBlockWithCid(blkData, rb.CID()) if err != nil { - return nil, fmt.Errorf("assembling block: %w", err) + return nil, fmt.Errorf("assembling Bitswap block: %w", err) } return blk, nil @@ -104,7 +104,7 @@ func (rb *RowBlock) IsEmpty() bool { return rb.Container == nil } -func (rb *RowBlock) Populate(root *share.Root) PopulateFn { +func (rb *RowBlock) PopulateFn(root *share.Root) PopulateFn { return func(data []byte) error { if !rb.IsEmpty() { return nil @@ -120,7 +120,7 @@ func (rb *RowBlock) Populate(root *share.Root) PopulateFn { } rb.Container = &cntr - // NOTE: We don't have to validate Block in the RowBlock, as it's implicitly verified by string + // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response // verification) return nil diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go index d1e93443b2..eb21a116c2 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -22,7 +22,7 @@ const ( ) func init() { - RegisterBlock( + registerBlock( rowNamespaceDataMultihashCode, rowNamespaceDataCodec, shwap.RowNamespaceDataIDSize, @@ -71,7 +71,7 @@ func EmptyRowNamespaceDataBlockFromCID(cid cid.Cid) (*RowNamespaceDataBlock, err func (rndb *RowNamespaceDataBlock) String() string { data, err := rndb.ID.MarshalBinary() if err != nil { - panic(fmt.Errorf("marshaling RowNamespaceDataBlock: %w", err)) + panic(fmt.Errorf("marshaling RowNamespaceDataID: %w", err)) } return string(data) @@ -89,22 +89,22 @@ func (rndb *RowNamespaceDataBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) rndID, err := rndb.ID.MarshalBinary() if err != nil { - return nil, fmt.Errorf("marshaling RowNamespaceDataBlock: %w", err) + return nil, fmt.Errorf("marshaling RowNamespaceDataID: %w", err) } - rndidBlk := bitswapb.RowNamespaceDataBlock{ + rndBlk := bitswapb.RowNamespaceDataBlock{ RowNamespaceDataId: rndID, Data: rnd.ToProto(), } - blkData, err := rndidBlk.Marshal() + blkData, err := rndBlk.Marshal() if err != nil { return nil, fmt.Errorf("marshaling RowNamespaceDataBlock: %w", err) } blk, err := blocks.NewBlockWithCid(blkData, rndb.CID()) if err != nil { - return nil, fmt.Errorf("assembling block: %w", err) + return nil, fmt.Errorf("assembling Bitswap block: %w", err) } return blk, nil @@ -114,7 +114,7 @@ func (rndb *RowNamespaceDataBlock) IsEmpty() bool { return rndb.Container == nil } -func (rndb *RowNamespaceDataBlock) Populate(root *share.Root) PopulateFn { +func (rndb *RowNamespaceDataBlock) PopulateFn(root *share.Root) PopulateFn { return func(data []byte) error { if !rndb.IsEmpty() { return nil @@ -130,7 +130,7 @@ func (rndb *RowNamespaceDataBlock) Populate(root *share.Root) PopulateFn { } rndb.Container = &cntr - // NOTE: We don't have to validate Block in the RowBlock, as it's implicitly verified by string + // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response // verification) return nil diff --git a/share/shwap/p2p/bitswap/sample_block.go b/share/shwap/p2p/bitswap/sample_block.go index 8d70b4a66b..1729cc650b 100644 --- a/share/shwap/p2p/bitswap/sample_block.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -23,7 +23,7 @@ const ( ) func init() { - RegisterBlock( + registerBlock( sampleMultihashCode, sampleCodec, shwap.SampleIDSize, @@ -67,7 +67,7 @@ func EmptySampleBlockFromCID(cid cid.Cid) (*SampleBlock, error) { func (sb *SampleBlock) String() string { data, err := sb.ID.MarshalBinary() if err != nil { - panic(fmt.Errorf("marshaling SampleBlock: %w", err)) + panic(fmt.Errorf("marshaling SampleID: %w", err)) } return string(data) } @@ -84,7 +84,7 @@ func (sb *SampleBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Bloc smplID, err := sb.ID.MarshalBinary() if err != nil { - return nil, fmt.Errorf("marshaling SampleBlock: %w", err) + return nil, fmt.Errorf("marshaling SampleID: %w", err) } smplBlk := bitswappb.SampleBlock{ @@ -99,7 +99,7 @@ func (sb *SampleBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Bloc blk, err := blocks.NewBlockWithCid(blkData, sb.CID()) if err != nil { - return nil, fmt.Errorf("assembling block: %w", err) + return nil, fmt.Errorf("assembling Bitswap block: %w", err) } return blk, nil @@ -109,7 +109,7 @@ func (sb *SampleBlock) IsEmpty() bool { return sb.Container == nil } -func (sb *SampleBlock) Populate(root *share.Root) PopulateFn { +func (sb *SampleBlock) PopulateFn(root *share.Root) PopulateFn { return func(data []byte) error { if !sb.IsEmpty() { return nil @@ -124,7 +124,8 @@ func (sb *SampleBlock) Populate(root *share.Root) PopulateFn { return fmt.Errorf("validating Sample: %w", err) } sb.Container = &cntr - // NOTE: We don't have to validate Block in the RowBlock, as it's implicitly verified by string + + // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response // verification) return nil From e14c01013cee6142598a5a93212a8594902b3968 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Wed, 5 Jun 2024 01:48:19 +0200 Subject: [PATCH 12/40] cid over the wire --- share/shwap/p2p/bitswap/block.go | 4 - share/shwap/p2p/bitswap/block_fetch.go | 56 ++++++--- share/shwap/p2p/bitswap/pb/bitswap.pb.go | 118 +++++++++--------- share/shwap/p2p/bitswap/pb/bitswap.proto | 6 +- share/shwap/p2p/bitswap/row_block.go | 20 +-- .../p2p/bitswap/row_namespace_data_block.go | 21 +--- share/shwap/p2p/bitswap/sample_block.go | 20 +-- 7 files changed, 112 insertions(+), 133 deletions(-) diff --git a/share/shwap/p2p/bitswap/block.go b/share/shwap/p2p/bitswap/block.go index 91ed76c8f9..9bd870c9fa 100644 --- a/share/shwap/p2p/bitswap/block.go +++ b/share/shwap/p2p/bitswap/block.go @@ -20,10 +20,6 @@ type PopulateFn func([]byte) error // All Shwap containers must have a registerBlock-ed wrapper // implementing the interface to be compatible with Bitswap. type Block interface { - // String returns string representation of the Block - // to be used as map key. Might not be human-readable. - String() string - // CID returns Shwap ID of the Block formatted as CID. CID() cid.Cid // BlockFromEDS extract Bitswap Block out of the EDS. diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 3390b9c450..8ee15d579a 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -3,6 +3,7 @@ package bitswap import ( "context" "crypto/sha256" + "encoding/binary" "fmt" "sync" @@ -25,20 +26,19 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks continue // skip populated Blocks } - idStr := blk.String() - p := blk.PopulateFn(root) cid := blk.CID() cids = append(cids, cid) + p := blk.PopulateFn(root) // store the PopulateFn s.t. hasher can access it // and fill in the Block - _, exists := populatorFns.LoadOrStore(idStr, p) + _, exists := populatorFns.LoadOrStore(cid, p) if exists { // in case there is ongoing fetch happening for the same Block elsewhere // and PopulateFn has already been set -- mark the Block as duplicate duplicate[cid] = blk } else { // only do the cleanup if we stored the PopulateFn - defer populatorFns.Delete(idStr) + defer populatorFns.Delete(cid) } } @@ -93,31 +93,51 @@ type hasher struct { } func (h *hasher) Write(data []byte) (int, error) { - const pbOffset = 2 // this assumes the protobuf serialization is in use - if len(data) < h.IDSize+pbOffset { - err := fmt.Errorf("shwap/bitswap hasher: insufficient data size") - log.Error() - return 0, err + if len(data) == 0 { + errMsg := "hasher: empty message" + log.Error(errMsg) + return 0, fmt.Errorf("shwap/bitswap: %s", errMsg) + } + + const pbTypeOffset = 1 // this assumes the protobuf serialization is in use + cidLen, ln := binary.Uvarint(data[pbTypeOffset:]) + if ln <= 0 || len(data) < pbTypeOffset+ln+int(cidLen) { + errMsg := fmt.Sprintf("hasher: invalid message length: %d", ln) + log.Error(errMsg) + return 0, fmt.Errorf("shwap/bitswap: %s", errMsg) } - // extract Block out of data + // extract CID out of data // we do this on the raw data to: // * Avoid complicating hasher with generalized bytes -> type unmarshalling // * Avoid type allocations - id := data[pbOffset : h.IDSize+pbOffset] + cidRaw := data[pbTypeOffset+ln : pbTypeOffset+ln+int(cidLen)] + cid, err := cid.Cast(cidRaw) + if err != nil { + err = fmt.Errorf("hasher: casting cid: %w", err) + log.Error(err) + return 0, fmt.Errorf("shwap/bitswap: %w", err) + } + // get ID out of CID and validate it + id, err := extractCID(cid) + if err != nil { + err = fmt.Errorf("hasher: validating cid: %w", err) + log.Error(err) + return 0, fmt.Errorf("shwap/bitswap: %w", err) + } // get registered PopulateFn and use it to check data validity and // pass it to Fetch caller - val, ok := populatorFns.Load(string(id)) + val, ok := populatorFns.Load(cid) if !ok { - err := fmt.Errorf("shwap/bitswap hasher: no verifier registered") - log.Error(err) - return 0, err + errMsg := "hasher: no verifier registered" + log.Error(errMsg) + return 0, fmt.Errorf("shwap/bitswap: %s", errMsg) } populate := val.(PopulateFn) - err := populate(data) + err = populate(data) if err != nil { - err = fmt.Errorf("shwap/bitswap hasher: verifying data: %w", err) + err = fmt.Errorf("hasher: verifying data: %w", err) log.Error(err) - return 0, err + return 0, fmt.Errorf("shwap/bitswap: %w", err) } // set the id as resulting sum // it's required for the sum to match the requested ID diff --git a/share/shwap/p2p/bitswap/pb/bitswap.pb.go b/share/shwap/p2p/bitswap/pb/bitswap.pb.go index b7ddf0f633..d159661361 100644 --- a/share/shwap/p2p/bitswap/pb/bitswap.pb.go +++ b/share/shwap/p2p/bitswap/pb/bitswap.pb.go @@ -24,8 +24,8 @@ var _ = math.Inf const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type RowBlock struct { - RowId []byte `protobuf:"bytes,1,opt,name=row_id,json=rowId,proto3" json:"row_id,omitempty"` - Row *pb.Row `protobuf:"bytes,2,opt,name=row,proto3" json:"row,omitempty"` + RowCid []byte `protobuf:"bytes,1,opt,name=row_cid,json=rowCid,proto3" json:"row_cid,omitempty"` + Row *pb.Row `protobuf:"bytes,2,opt,name=row,proto3" json:"row,omitempty"` } func (m *RowBlock) Reset() { *m = RowBlock{} } @@ -61,9 +61,9 @@ func (m *RowBlock) XXX_DiscardUnknown() { var xxx_messageInfo_RowBlock proto.InternalMessageInfo -func (m *RowBlock) GetRowId() []byte { +func (m *RowBlock) GetRowCid() []byte { if m != nil { - return m.RowId + return m.RowCid } return nil } @@ -76,8 +76,8 @@ func (m *RowBlock) GetRow() *pb.Row { } type SampleBlock struct { - SampleId []byte `protobuf:"bytes,1,opt,name=sample_id,json=sampleId,proto3" json:"sample_id,omitempty"` - Sample *pb.Sample `protobuf:"bytes,2,opt,name=sample,proto3" json:"sample,omitempty"` + SampleCid []byte `protobuf:"bytes,1,opt,name=sample_cid,json=sampleCid,proto3" json:"sample_cid,omitempty"` + Sample *pb.Sample `protobuf:"bytes,2,opt,name=sample,proto3" json:"sample,omitempty"` } func (m *SampleBlock) Reset() { *m = SampleBlock{} } @@ -113,9 +113,9 @@ func (m *SampleBlock) XXX_DiscardUnknown() { var xxx_messageInfo_SampleBlock proto.InternalMessageInfo -func (m *SampleBlock) GetSampleId() []byte { +func (m *SampleBlock) GetSampleCid() []byte { if m != nil { - return m.SampleId + return m.SampleCid } return nil } @@ -128,8 +128,8 @@ func (m *SampleBlock) GetSample() *pb.Sample { } type RowNamespaceDataBlock struct { - RowNamespaceDataId []byte `protobuf:"bytes,1,opt,name=row_namespace_data_id,json=rowNamespaceDataId,proto3" json:"row_namespace_data_id,omitempty"` - Data *pb.RowNamespaceData `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + RowNamespaceDataCid []byte `protobuf:"bytes,1,opt,name=row_namespace_data_cid,json=rowNamespaceDataCid,proto3" json:"row_namespace_data_cid,omitempty"` + Data *pb.RowNamespaceData `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` } func (m *RowNamespaceDataBlock) Reset() { *m = RowNamespaceDataBlock{} } @@ -165,9 +165,9 @@ func (m *RowNamespaceDataBlock) XXX_DiscardUnknown() { var xxx_messageInfo_RowNamespaceDataBlock proto.InternalMessageInfo -func (m *RowNamespaceDataBlock) GetRowNamespaceDataId() []byte { +func (m *RowNamespaceDataBlock) GetRowNamespaceDataCid() []byte { if m != nil { - return m.RowNamespaceDataId + return m.RowNamespaceDataCid } return nil } @@ -190,26 +190,26 @@ func init() { } var fileDescriptor_09fd4e2ff1d5ce94 = []byte{ - // 295 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xcf, 0x4a, 0x03, 0x31, - 0x10, 0xc6, 0x1b, 0xff, 0xd4, 0x9a, 0xea, 0x25, 0x50, 0x2c, 0x55, 0x42, 0x29, 0x08, 0x05, 0xb1, - 0x8b, 0xf5, 0x01, 0x0a, 0xc5, 0xcb, 0x5e, 0x04, 0xe3, 0x49, 0x2f, 0x25, 0xbb, 0x09, 0xdd, 0xc5, - 0xdd, 0x4e, 0x48, 0x22, 0x79, 0x0d, 0x1f, 0xcb, 0x63, 0x8f, 0x1e, 0x65, 0xf7, 0x45, 0x64, 0x37, - 0x5b, 0x97, 0x0a, 0xde, 0xbe, 0xcc, 0x7c, 0xdf, 0x6f, 0x86, 0x09, 0x9e, 0x9a, 0x84, 0x6b, 0x19, - 0x98, 0xc4, 0x71, 0x15, 0xa8, 0xb9, 0x0a, 0xa2, 0xd4, 0x9a, 0x5a, 0x47, 0x3b, 0x39, 0x53, 0x1a, - 0x2c, 0x90, 0x93, 0xe6, 0x39, 0x1a, 0xed, 0x45, 0x22, 0x2f, 0xbc, 0x69, 0xb2, 0xc0, 0x3d, 0x06, - 0x6e, 0x99, 0x41, 0xfc, 0x46, 0x06, 0xb8, 0xab, 0xc1, 0xad, 0x52, 0x31, 0x44, 0x63, 0x34, 0x3d, - 0x63, 0xc7, 0x1a, 0x5c, 0x28, 0xc8, 0x15, 0x3e, 0xd4, 0xe0, 0x86, 0x07, 0x63, 0x34, 0xed, 0xcf, - 0xf1, 0xcc, 0xa7, 0x19, 0x38, 0x56, 0x95, 0x27, 0x4f, 0xb8, 0xff, 0xcc, 0x73, 0x95, 0x49, 0xcf, - 0xb8, 0xc4, 0xa7, 0xa6, 0x7e, 0xb6, 0x98, 0x9e, 0x2f, 0x84, 0x82, 0x5c, 0xe3, 0xae, 0xd7, 0x0d, - 0xec, 0xbc, 0x81, 0x79, 0x00, 0x6b, 0x9a, 0x13, 0x87, 0x07, 0x0c, 0xdc, 0x23, 0xcf, 0xa5, 0x51, - 0x3c, 0x96, 0x0f, 0xdc, 0x72, 0x0f, 0xbf, 0xc3, 0x83, 0x6a, 0xc1, 0xcd, 0xae, 0xb3, 0x12, 0xdc, - 0xf2, 0x76, 0x10, 0xd1, 0x7f, 0x52, 0xa1, 0x20, 0x37, 0xf8, 0xa8, 0x32, 0x35, 0x03, 0x2f, 0xda, - 0xed, 0xf7, 0x8c, 0xac, 0x36, 0x2d, 0x5f, 0x3e, 0x0b, 0x8a, 0xb6, 0x05, 0x45, 0xdf, 0x05, 0x45, - 0x1f, 0x25, 0xed, 0x6c, 0x4b, 0xda, 0xf9, 0x2a, 0x69, 0xe7, 0x75, 0xb1, 0x4e, 0x6d, 0xf2, 0x1e, - 0xcd, 0x62, 0xc8, 0x83, 0x58, 0x66, 0xd2, 0xd8, 0x94, 0x83, 0x5e, 0xff, 0xea, 0xdb, 0x0d, 0x88, - 0xea, 0xc2, 0xff, 0x7d, 0x4d, 0xd4, 0xad, 0xcf, 0x7d, 0xff, 0x13, 0x00, 0x00, 0xff, 0xff, 0xbe, - 0x9c, 0xa6, 0x0a, 0xbf, 0x01, 0x00, 0x00, + // 296 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xc1, 0x4a, 0xc3, 0x40, + 0x10, 0x86, 0xbb, 0x2a, 0xad, 0x4e, 0xf5, 0x52, 0xd1, 0x96, 0xa2, 0x4b, 0x29, 0x08, 0x05, 0xb1, + 0x81, 0xf6, 0x01, 0xc4, 0xea, 0xd9, 0xc3, 0xf6, 0xa4, 0x97, 0xb2, 0x49, 0x96, 0x26, 0x98, 0x74, + 0x96, 0xdd, 0x95, 0xc5, 0xb7, 0xf0, 0xb1, 0x3c, 0xf6, 0xe8, 0x51, 0x92, 0x17, 0x91, 0xcd, 0xa6, + 0xda, 0x0a, 0xde, 0xfe, 0x99, 0xf9, 0xe7, 0xfb, 0x93, 0x59, 0x18, 0xe9, 0x84, 0x2b, 0x11, 0xe8, + 0xc4, 0x72, 0x19, 0xc8, 0x89, 0x0c, 0xc2, 0xd4, 0xe8, 0x4a, 0x87, 0x1b, 0x39, 0x96, 0x0a, 0x0d, + 0x76, 0x5a, 0x75, 0xd9, 0xef, 0xef, 0xac, 0x84, 0x5e, 0x78, 0xd3, 0xf0, 0x0e, 0x0e, 0x19, 0xda, + 0x59, 0x86, 0xd1, 0x4b, 0xa7, 0x0b, 0x2d, 0x85, 0x76, 0x11, 0xa5, 0x71, 0x8f, 0x0c, 0xc8, 0xe8, + 0x98, 0x35, 0x15, 0xda, 0xfb, 0x34, 0xee, 0x5c, 0xc0, 0xbe, 0x42, 0xdb, 0xdb, 0x1b, 0x90, 0x51, + 0x7b, 0x02, 0x63, 0xbf, 0xcf, 0xd0, 0x32, 0xd7, 0x1e, 0xce, 0xa1, 0x3d, 0xe7, 0xb9, 0xcc, 0x84, + 0xa7, 0x5c, 0x02, 0xe8, 0xaa, 0xdc, 0x02, 0x1d, 0xf9, 0x8e, 0x63, 0x5d, 0x41, 0xd3, 0x17, 0x35, + 0xee, 0xa4, 0xc6, 0x79, 0x04, 0xab, 0x87, 0xc3, 0x37, 0x38, 0x63, 0x68, 0x1f, 0x79, 0x2e, 0xb4, + 0xe4, 0x91, 0x78, 0xe0, 0x86, 0x7b, 0xfc, 0x14, 0xce, 0xdd, 0x47, 0xae, 0x36, 0x93, 0x45, 0xcc, + 0x0d, 0xdf, 0x8a, 0x3a, 0x55, 0x7f, 0xd6, 0x5c, 0xe8, 0x35, 0x1c, 0x38, 0x5b, 0x1d, 0xd9, 0xfd, + 0xfd, 0x83, 0x1d, 0x27, 0xab, 0x4c, 0xb3, 0xa7, 0x8f, 0x82, 0x92, 0x75, 0x41, 0xc9, 0x57, 0x41, + 0xc9, 0x7b, 0x49, 0x1b, 0xeb, 0x92, 0x36, 0x3e, 0x4b, 0xda, 0x78, 0xbe, 0x5d, 0xa6, 0x26, 0x79, + 0x0d, 0xc7, 0x11, 0xe6, 0x41, 0x24, 0x32, 0xa1, 0x4d, 0xca, 0x51, 0x2d, 0x7f, 0xf4, 0xcd, 0x0a, + 0x63, 0x77, 0xe7, 0xff, 0x1e, 0x28, 0x6c, 0x56, 0x47, 0x9f, 0x7e, 0x07, 0x00, 0x00, 0xff, 0xff, + 0x7d, 0xd6, 0xa6, 0x3b, 0xc5, 0x01, 0x00, 0x00, } func (m *RowBlock) Marshal() (dAtA []byte, err error) { @@ -244,10 +244,10 @@ func (m *RowBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if len(m.RowId) > 0 { - i -= len(m.RowId) - copy(dAtA[i:], m.RowId) - i = encodeVarintBitswap(dAtA, i, uint64(len(m.RowId))) + if len(m.RowCid) > 0 { + i -= len(m.RowCid) + copy(dAtA[i:], m.RowCid) + i = encodeVarintBitswap(dAtA, i, uint64(len(m.RowCid))) i-- dAtA[i] = 0xa } @@ -286,10 +286,10 @@ func (m *SampleBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if len(m.SampleId) > 0 { - i -= len(m.SampleId) - copy(dAtA[i:], m.SampleId) - i = encodeVarintBitswap(dAtA, i, uint64(len(m.SampleId))) + if len(m.SampleCid) > 0 { + i -= len(m.SampleCid) + copy(dAtA[i:], m.SampleCid) + i = encodeVarintBitswap(dAtA, i, uint64(len(m.SampleCid))) i-- dAtA[i] = 0xa } @@ -328,10 +328,10 @@ func (m *RowNamespaceDataBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if len(m.RowNamespaceDataId) > 0 { - i -= len(m.RowNamespaceDataId) - copy(dAtA[i:], m.RowNamespaceDataId) - i = encodeVarintBitswap(dAtA, i, uint64(len(m.RowNamespaceDataId))) + if len(m.RowNamespaceDataCid) > 0 { + i -= len(m.RowNamespaceDataCid) + copy(dAtA[i:], m.RowNamespaceDataCid) + i = encodeVarintBitswap(dAtA, i, uint64(len(m.RowNamespaceDataCid))) i-- dAtA[i] = 0xa } @@ -355,7 +355,7 @@ func (m *RowBlock) Size() (n int) { } var l int _ = l - l = len(m.RowId) + l = len(m.RowCid) if l > 0 { n += 1 + l + sovBitswap(uint64(l)) } @@ -372,7 +372,7 @@ func (m *SampleBlock) Size() (n int) { } var l int _ = l - l = len(m.SampleId) + l = len(m.SampleCid) if l > 0 { n += 1 + l + sovBitswap(uint64(l)) } @@ -389,7 +389,7 @@ func (m *RowNamespaceDataBlock) Size() (n int) { } var l int _ = l - l = len(m.RowNamespaceDataId) + l = len(m.RowNamespaceDataCid) if l > 0 { n += 1 + l + sovBitswap(uint64(l)) } @@ -437,7 +437,7 @@ func (m *RowBlock) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RowId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RowCid", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -464,9 +464,9 @@ func (m *RowBlock) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.RowId = append(m.RowId[:0], dAtA[iNdEx:postIndex]...) - if m.RowId == nil { - m.RowId = []byte{} + m.RowCid = append(m.RowCid[:0], dAtA[iNdEx:postIndex]...) + if m.RowCid == nil { + m.RowCid = []byte{} } iNdEx = postIndex case 2: @@ -557,7 +557,7 @@ func (m *SampleBlock) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SampleId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SampleCid", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -584,9 +584,9 @@ func (m *SampleBlock) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.SampleId = append(m.SampleId[:0], dAtA[iNdEx:postIndex]...) - if m.SampleId == nil { - m.SampleId = []byte{} + m.SampleCid = append(m.SampleCid[:0], dAtA[iNdEx:postIndex]...) + if m.SampleCid == nil { + m.SampleCid = []byte{} } iNdEx = postIndex case 2: @@ -677,7 +677,7 @@ func (m *RowNamespaceDataBlock) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RowNamespaceDataId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RowNamespaceDataCid", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -704,9 +704,9 @@ func (m *RowNamespaceDataBlock) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.RowNamespaceDataId = append(m.RowNamespaceDataId[:0], dAtA[iNdEx:postIndex]...) - if m.RowNamespaceDataId == nil { - m.RowNamespaceDataId = []byte{} + m.RowNamespaceDataCid = append(m.RowNamespaceDataCid[:0], dAtA[iNdEx:postIndex]...) + if m.RowNamespaceDataCid == nil { + m.RowNamespaceDataCid = []byte{} } iNdEx = postIndex case 2: diff --git a/share/shwap/p2p/bitswap/pb/bitswap.proto b/share/shwap/p2p/bitswap/pb/bitswap.proto index ffa922fd02..8f2fb78748 100644 --- a/share/shwap/p2p/bitswap/pb/bitswap.proto +++ b/share/shwap/p2p/bitswap/pb/bitswap.proto @@ -5,16 +5,16 @@ option go_package = "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswa import "share/shwap/pb/shwap.proto"; message RowBlock { - bytes row_id = 1; + bytes row_cid = 1; shwap.Row row = 2; } message SampleBlock { - bytes sample_id = 1; + bytes sample_cid = 1; shwap.Sample sample = 2; } message RowNamespaceDataBlock { - bytes row_namespace_data_id = 1; + bytes row_namespace_data_cid = 1; shwap.RowNamespaceData data = 2; } diff --git a/share/shwap/p2p/bitswap/row_block.go b/share/shwap/p2p/bitswap/row_block.go index 86fe9c205e..284864c461 100644 --- a/share/shwap/p2p/bitswap/row_block.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -62,14 +62,6 @@ func EmptyRowBlockFromCID(cid cid.Cid) (*RowBlock, error) { return &RowBlock{ID: rid}, nil } -func (rb *RowBlock) String() string { - data, err := rb.ID.MarshalBinary() - if err != nil { - panic(fmt.Errorf("marshaling RowID: %w", err)) - } - return string(data) -} - func (rb *RowBlock) CID() cid.Cid { return encodeCID(rb.ID, rowMultihashCode, rowCodec) } @@ -77,14 +69,10 @@ func (rb *RowBlock) CID() cid.Cid { func (rb *RowBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { row := shwap.RowFromEDS(eds, rb.ID.RowIndex, shwap.Left) - rowID, err := rb.ID.MarshalBinary() - if err != nil { - return nil, fmt.Errorf("marshaling RowID: %w", err) - } - + cid := rb.CID() rowBlk := bitswappb.RowBlock{ - RowId: rowID, - Row: row.ToProto(), + RowCid: cid.Bytes(), + Row: row.ToProto(), } blkData, err := rowBlk.Marshal() @@ -92,7 +80,7 @@ func (rb *RowBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, return nil, fmt.Errorf("marshaling RowBlock: %w", err) } - blk, err := blocks.NewBlockWithCid(blkData, rb.CID()) + blk, err := blocks.NewBlockWithCid(blkData, cid) if err != nil { return nil, fmt.Errorf("assembling Bitswap block: %w", err) } diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go index eb21a116c2..cffb53e5da 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -68,15 +68,6 @@ func EmptyRowNamespaceDataBlockFromCID(cid cid.Cid) (*RowNamespaceDataBlock, err return &RowNamespaceDataBlock{ID: rndid}, nil } -func (rndb *RowNamespaceDataBlock) String() string { - data, err := rndb.ID.MarshalBinary() - if err != nil { - panic(fmt.Errorf("marshaling RowNamespaceDataID: %w", err)) - } - - return string(data) -} - func (rndb *RowNamespaceDataBlock) CID() cid.Cid { return encodeCID(rndb.ID, rowNamespaceDataMultihashCode, rowNamespaceDataCodec) } @@ -87,14 +78,10 @@ func (rndb *RowNamespaceDataBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) return nil, err } - rndID, err := rndb.ID.MarshalBinary() - if err != nil { - return nil, fmt.Errorf("marshaling RowNamespaceDataID: %w", err) - } - + cid := rndb.CID() rndBlk := bitswapb.RowNamespaceDataBlock{ - RowNamespaceDataId: rndID, - Data: rnd.ToProto(), + RowNamespaceDataCid: cid.Bytes(), + Data: rnd.ToProto(), } blkData, err := rndBlk.Marshal() @@ -102,7 +89,7 @@ func (rndb *RowNamespaceDataBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) return nil, fmt.Errorf("marshaling RowNamespaceDataBlock: %w", err) } - blk, err := blocks.NewBlockWithCid(blkData, rndb.CID()) + blk, err := blocks.NewBlockWithCid(blkData, cid) if err != nil { return nil, fmt.Errorf("assembling Bitswap block: %w", err) } diff --git a/share/shwap/p2p/bitswap/sample_block.go b/share/shwap/p2p/bitswap/sample_block.go index 1729cc650b..3006eb38ec 100644 --- a/share/shwap/p2p/bitswap/sample_block.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -64,14 +64,6 @@ func EmptySampleBlockFromCID(cid cid.Cid) (*SampleBlock, error) { return &SampleBlock{ID: sid}, nil } -func (sb *SampleBlock) String() string { - data, err := sb.ID.MarshalBinary() - if err != nil { - panic(fmt.Errorf("marshaling SampleID: %w", err)) - } - return string(data) -} - func (sb *SampleBlock) CID() cid.Cid { return encodeCID(sb.ID, sampleMultihashCode, sampleCodec) } @@ -82,14 +74,10 @@ func (sb *SampleBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Bloc return nil, err } - smplID, err := sb.ID.MarshalBinary() - if err != nil { - return nil, fmt.Errorf("marshaling SampleID: %w", err) - } - + cid := sb.CID() smplBlk := bitswappb.SampleBlock{ - SampleId: smplID, - Sample: smpl.ToProto(), + SampleCid: cid.Bytes(), + Sample: smpl.ToProto(), } blkData, err := smplBlk.Marshal() @@ -97,7 +85,7 @@ func (sb *SampleBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Bloc return nil, fmt.Errorf("marshaling SampleBlock: %w", err) } - blk, err := blocks.NewBlockWithCid(blkData, sb.CID()) + blk, err := blocks.NewBlockWithCid(blkData, cid) if err != nil { return nil, fmt.Errorf("assembling Bitswap block: %w", err) } From 21bd564de93bbd3635d0a802c16f11943ad37125 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Wed, 5 Jun 2024 17:55:07 +0200 Subject: [PATCH 13/40] simplify and deflakify(some cases) duplicate test --- share/shwap/p2p/bitswap/block_fetch.go | 34 ++++++++----------- share/shwap/p2p/bitswap/block_fetch_test.go | 32 +++++++++-------- share/shwap/p2p/bitswap/row_block.go | 14 +++++--- share/shwap/p2p/bitswap/row_block_test.go | 2 +- .../p2p/bitswap/row_namespace_data_block.go | 14 +++++--- .../bitswap/row_namespace_data_block_test.go | 2 +- share/shwap/p2p/bitswap/sample_block.go | 11 ++++-- share/shwap/p2p/bitswap/sample_block_test.go | 2 +- 8 files changed, 63 insertions(+), 48 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 8ee15d579a..7c6c4938d6 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -20,26 +20,19 @@ import ( // Blocks until either context is canceled or all Blocks are fetched and populated. func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks ...Block) error { cids := make([]cid.Cid, 0, len(blks)) - duplicate := make(map[cid.Cid]Block) + fetching := make(map[cid.Cid]Block) for _, blk := range blks { if !blk.IsEmpty() { continue // skip populated Blocks } - + // memoize CID for reuse as it ain't free cid := blk.CID() + fetching[cid] = blk // mark block as fetching cids = append(cids, cid) - p := blk.PopulateFn(root) // store the PopulateFn s.t. hasher can access it // and fill in the Block - _, exists := populatorFns.LoadOrStore(cid, p) - if exists { - // in case there is ongoing fetch happening for the same Block elsewhere - // and PopulateFn has already been set -- mark the Block as duplicate - duplicate[cid] = blk - } else { - // only do the cleanup if we stored the PopulateFn - defer populatorFns.Delete(cid) - } + populate := blk.PopulateFn(root) + populatorFns.LoadOrStore(cid, populate) } blkCh, err := fetcher.GetBlocks(ctx, cids) @@ -47,16 +40,17 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks return fmt.Errorf("fetching bitswap blocks: %w", err) } - for blk := range blkCh { // GetBlocks closes blkCh on ctx cancellation - // check if the blk is a duplicate - id, ok := duplicate[blk.Cid()] - if !ok { + for bitswapBlk := range blkCh { // GetBlocks closes blkCh on ctx cancellation + blk := fetching[bitswapBlk.Cid()] + if !blk.IsEmpty() { + // common case: the block was populated by the hasher, so skip continue } - // if it is, we have to populate it ourselves instead of hasher, - // as there is only one PopulateFN allowed per ID - err := id.PopulateFn(root)(blk.RawData()) + // uncommon duplicate case: concurrent fetching of the same block, + // so we have to populate it ourselves instead of the hasher, + err := blk.PopulateFn(root)(bitswapBlk.RawData()) if err != nil { + // this means verification succeeded in the hasher but failed here // this case should never happen in practice // and if so something is really wrong panic(fmt.Sprintf("populating duplicate block: %s", err)) @@ -126,7 +120,7 @@ func (h *hasher) Write(data []byte) (int, error) { } // get registered PopulateFn and use it to check data validity and // pass it to Fetch caller - val, ok := populatorFns.Load(cid) + val, ok := populatorFns.LoadAndDelete(cid) if !ok { errMsg := "hasher: no verifier registered" log.Error(errMsg) diff --git a/share/shwap/p2p/bitswap/block_fetch_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go index 82463ead0d..fa1594499d 100644 --- a/share/shwap/p2p/bitswap/block_fetch_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/rand/v2" + "runtime" "sync" "testing" "time" @@ -29,7 +30,8 @@ import ( ) func TestFetchDuplicates(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + runtime.GOMAXPROCS(3) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) defer cancel() eds := edstest.RandEDS(t, 4) @@ -38,31 +40,33 @@ func TestFetchDuplicates(t *testing.T) { fetcher := fetcher(ctx, t, newTestBlockstore(eds)) var wg sync.WaitGroup - blks := make([]*RowBlock, 100) - for i := range blks { - blk, err := NewEmptyRowBlock(1, 0, root) // create the same Block ID - require.NoError(t, err) - blks[i] = blk + for i := range 100 { + blks := make([]Block, eds.Width()) + for i := range blks { + blk, err := NewEmptyRowBlock(1, i, root) // create the same Block ID + require.NoError(t, err) + blks[i] = blk + } wg.Add(1) - go func() { + go func(i int) { rint := rand.IntN(10) // this sleep ensures fetches aren't started simultaneously allowing to check for edge-cases time.Sleep(time.Millisecond * time.Duration(rint)) - err := Fetch(ctx, fetcher, root, blk) + err := Fetch(ctx, fetcher, root, blks...) assert.NoError(t, err) + for _, blk := range blks { + assert.False(t, blk.IsEmpty()) + } wg.Done() - }() + }(i) } wg.Wait() - for _, blk := range blks { - assert.False(t, blk.IsEmpty()) - } - var entries int - populatorFns.Range(func(any, any) bool { + populatorFns.Range(func(key, _ any) bool { + populatorFns.Delete(key) entries++ return true }) diff --git a/share/shwap/p2p/bitswap/row_block.go b/share/shwap/p2p/bitswap/row_block.go index 284864c461..0a5599a5f2 100644 --- a/share/shwap/p2p/bitswap/row_block.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -2,6 +2,7 @@ package bitswap import ( "fmt" + "sync/atomic" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" @@ -34,8 +35,9 @@ func init() { // RowBlock is a Bitswap compatible block for Shwap's Row container. type RowBlock struct { - ID shwap.RowID - Container *shwap.Row + ID shwap.RowID + + container atomic.Pointer[shwap.Row] } // NewEmptyRowBlock constructs a new empty RowBlock. @@ -89,7 +91,11 @@ func (rb *RowBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, } func (rb *RowBlock) IsEmpty() bool { - return rb.Container == nil + return rb.Container() == nil +} + +func (rb *RowBlock) Container() *shwap.Row { + return rb.container.Load() } func (rb *RowBlock) PopulateFn(root *share.Root) PopulateFn { @@ -106,7 +112,7 @@ func (rb *RowBlock) PopulateFn(root *share.Root) PopulateFn { if err := cntr.Validate(root, rb.ID.RowIndex); err != nil { return fmt.Errorf("validating Row: %w", err) } - rb.Container = &cntr + rb.container.Store(&cntr) // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response diff --git a/share/shwap/p2p/bitswap/row_block_test.go b/share/shwap/p2p/bitswap/row_block_test.go index e2432955cd..da30cbaeac 100644 --- a/share/shwap/p2p/bitswap/row_block_test.go +++ b/share/shwap/p2p/bitswap/row_block_test.go @@ -32,7 +32,7 @@ func TestRowRoundtrip_GetContainers(t *testing.T) { for _, blk := range blks { row := blk.(*RowBlock) - err = row.Container.Validate(root, row.ID.RowIndex) + err = row.Container().Validate(root, row.ID.RowIndex) require.NoError(t, err) } } diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go index cffb53e5da..38568a344b 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -2,6 +2,7 @@ package bitswap import ( "fmt" + "sync/atomic" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" @@ -34,8 +35,9 @@ func init() { // RowNamespaceDataBlock is a Bitswap compatible block for Shwap's RowNamespaceData container. type RowNamespaceDataBlock struct { - ID shwap.RowNamespaceDataID - Container *shwap.RowNamespaceData + ID shwap.RowNamespaceDataID + + container atomic.Pointer[shwap.RowNamespaceData] } // NewEmptyRowNamespaceDataBlock constructs a new empty RowNamespaceDataBlock. @@ -98,7 +100,11 @@ func (rndb *RowNamespaceDataBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) } func (rndb *RowNamespaceDataBlock) IsEmpty() bool { - return rndb.Container == nil + return rndb.Container() == nil +} + +func (rndb *RowNamespaceDataBlock) Container() *shwap.RowNamespaceData { + return rndb.container.Load() } func (rndb *RowNamespaceDataBlock) PopulateFn(root *share.Root) PopulateFn { @@ -115,7 +121,7 @@ func (rndb *RowNamespaceDataBlock) PopulateFn(root *share.Root) PopulateFn { if err := cntr.Validate(root, rndb.ID.DataNamespace, rndb.ID.RowIndex); err != nil { return fmt.Errorf("validating RowNamespaceData: %w", err) } - rndb.Container = &cntr + rndb.container.Store(&cntr) // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block_test.go b/share/shwap/p2p/bitswap/row_namespace_data_block_test.go index 1d1f96b0ff..df5971db09 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block_test.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block_test.go @@ -33,7 +33,7 @@ func TestRowNamespaceDataRoundtrip_GetContainers(t *testing.T) { for _, blk := range blks { rnd := blk.(*RowNamespaceDataBlock) - err = rnd.Container.Validate(root, rnd.ID.DataNamespace, rnd.ID.RowIndex) + err = rnd.Container().Validate(root, rnd.ID.DataNamespace, rnd.ID.RowIndex) require.NoError(t, err) } } diff --git a/share/shwap/p2p/bitswap/sample_block.go b/share/shwap/p2p/bitswap/sample_block.go index 3006eb38ec..140290e7fa 100644 --- a/share/shwap/p2p/bitswap/sample_block.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -2,6 +2,7 @@ package bitswap import ( "fmt" + "sync/atomic" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" @@ -36,7 +37,7 @@ func init() { // SampleBlock is a Bitswap compatible block for Shwap's Sample container. type SampleBlock struct { ID shwap.SampleID - Container *shwap.Sample + container atomic.Pointer[shwap.Sample] } // NewEmptySampleBlock constructs a new empty SampleBlock. @@ -94,7 +95,11 @@ func (sb *SampleBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Bloc } func (sb *SampleBlock) IsEmpty() bool { - return sb.Container == nil + return sb.Container() == nil +} + +func (sb *SampleBlock) Container() *shwap.Sample { + return sb.container.Load() } func (sb *SampleBlock) PopulateFn(root *share.Root) PopulateFn { @@ -111,7 +116,7 @@ func (sb *SampleBlock) PopulateFn(root *share.Root) PopulateFn { if err := cntr.Validate(root, sb.ID.RowIndex, sb.ID.ShareIndex); err != nil { return fmt.Errorf("validating Sample: %w", err) } - sb.Container = &cntr + sb.container.Store(&cntr) // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response diff --git a/share/shwap/p2p/bitswap/sample_block_test.go b/share/shwap/p2p/bitswap/sample_block_test.go index 81dd957769..55156bbee1 100644 --- a/share/shwap/p2p/bitswap/sample_block_test.go +++ b/share/shwap/p2p/bitswap/sample_block_test.go @@ -35,7 +35,7 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { for _, sample := range blks { blk := sample.(*SampleBlock) - err = blk.Container.Validate(root, blk.ID.RowIndex, blk.ID.ShareIndex) + err = blk.Container().Validate(root, blk.ID.RowIndex, blk.ID.ShareIndex) require.NoError(t, err) } } From 3ac32ab46167b62d2658fdd80cbc19a1757a6432 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Wed, 5 Jun 2024 17:59:33 +0200 Subject: [PATCH 14/40] simplify note --- share/shwap/p2p/bitswap/row_block.go | 4 +--- share/shwap/p2p/bitswap/row_namespace_data_block.go | 4 +--- share/shwap/p2p/bitswap/sample_block.go | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/share/shwap/p2p/bitswap/row_block.go b/share/shwap/p2p/bitswap/row_block.go index 0a5599a5f2..b6eb37c8d3 100644 --- a/share/shwap/p2p/bitswap/row_block.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -114,9 +114,7 @@ func (rb *RowBlock) PopulateFn(root *share.Root) PopulateFn { } rb.container.Store(&cntr) - // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string - // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response - // verification) + // NOTE: We don't have to validate the ID here, as it is verified in the hasher. return nil } } diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go index 38568a344b..ebd5300c98 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -123,9 +123,7 @@ func (rndb *RowNamespaceDataBlock) PopulateFn(root *share.Root) PopulateFn { } rndb.container.Store(&cntr) - // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string - // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response - // verification) + // NOTE: We don't have to validate the ID here, as it is verified in the hasher. return nil } } diff --git a/share/shwap/p2p/bitswap/sample_block.go b/share/shwap/p2p/bitswap/sample_block.go index 140290e7fa..ae98c58bc1 100644 --- a/share/shwap/p2p/bitswap/sample_block.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -118,9 +118,7 @@ func (sb *SampleBlock) PopulateFn(root *share.Root) PopulateFn { } sb.container.Store(&cntr) - // NOTE: We don't have to validate ID in the RowBlock, as it's implicitly verified by string - // equality of globalVerifiers entry key(requesting side) and hasher accessing the entry(response - // verification) + // NOTE: We don't have to validate the ID here, as it is verified in the hasher. return nil } } From f1b3c49806179e684b369116d484be50e252c67d Mon Sep 17 00:00:00 2001 From: Wondertan Date: Tue, 11 Jun 2024 15:47:57 +0200 Subject: [PATCH 15/40] extract readCID func --- share/shwap/p2p/bitswap/block_fetch.go | 21 ++++++--------------- share/shwap/p2p/bitswap/cid.go | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 7c6c4938d6..758e535278 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -3,7 +3,6 @@ package bitswap import ( "context" "crypto/sha256" - "encoding/binary" "fmt" "sync" @@ -93,21 +92,13 @@ func (h *hasher) Write(data []byte) (int, error) { return 0, fmt.Errorf("shwap/bitswap: %s", errMsg) } - const pbTypeOffset = 1 // this assumes the protobuf serialization is in use - cidLen, ln := binary.Uvarint(data[pbTypeOffset:]) - if ln <= 0 || len(data) < pbTypeOffset+ln+int(cidLen) { - errMsg := fmt.Sprintf("hasher: invalid message length: %d", ln) - log.Error(errMsg) - return 0, fmt.Errorf("shwap/bitswap: %s", errMsg) - } - // extract CID out of data - // we do this on the raw data to: - // * Avoid complicating hasher with generalized bytes -> type unmarshalling - // * Avoid type allocations - cidRaw := data[pbTypeOffset+ln : pbTypeOffset+ln+int(cidLen)] - cid, err := cid.Cast(cidRaw) + // cut off the first tag type byte out of protobuf data + const pbTypeOffset = 1 + cidData := data[pbTypeOffset:] + + cid, err := readCID(cidData) if err != nil { - err = fmt.Errorf("hasher: casting cid: %w", err) + err = fmt.Errorf("hasher: reading cid: %w", err) log.Error(err) return 0, fmt.Errorf("shwap/bitswap: %w", err) } diff --git a/share/shwap/p2p/bitswap/cid.go b/share/shwap/p2p/bitswap/cid.go index 53129393da..bc72755854 100644 --- a/share/shwap/p2p/bitswap/cid.go +++ b/share/shwap/p2p/bitswap/cid.go @@ -2,6 +2,7 @@ package bitswap import ( "encoding" + "encoding/binary" "fmt" "github.com/ipfs/go-cid" @@ -22,6 +23,25 @@ func (a allowlist) IsAllowed(code uint64) bool { return ok } +// readCID reads out cid out of bytes +func readCID(data []byte) (cid.Cid, error) { + cidLen, ln := binary.Uvarint(data) + if ln <= 0 || len(data) < ln+int(cidLen) { + return cid.Undef, fmt.Errorf("invalid data length") + } + // extract CID out of data + // we do this on the raw data to: + // * Avoid complicating hasher with generalized bytes -> type unmarshalling + // * Avoid type allocations + cidRaw := data[ln : ln+int(cidLen)] + castCid, err := cid.Cast(cidRaw) + if err != nil { + return cid.Undef, fmt.Errorf("casting cid: %w", err) + } + + return castCid, nil +} + // extractCID retrieves Shwap ID out of the CID. func extractCID(cid cid.Cid) ([]byte, error) { if err := validateCID(cid); err != nil { From 9f8b0ec9e29359e4a6a0f3faeae6c922ca7f9575 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Wed, 12 Jun 2024 19:24:27 +0200 Subject: [PATCH 16/40] generalize protobuf message and respective refactorings --- share/shwap/p2p/bitswap/block.go | 31 + share/shwap/p2p/bitswap/block_fetch.go | 77 ++- share/shwap/p2p/bitswap/cid.go | 20 - share/shwap/p2p/bitswap/pb/bitswap.pb.go | 600 ++---------------- share/shwap/p2p/bitswap/pb/bitswap.proto | 18 +- share/shwap/p2p/bitswap/row_block.go | 28 +- .../p2p/bitswap/row_namespace_data_block.go | 28 +- share/shwap/p2p/bitswap/sample_block.go | 28 +- 8 files changed, 159 insertions(+), 671 deletions(-) diff --git a/share/shwap/p2p/bitswap/block.go b/share/shwap/p2p/bitswap/block.go index 9bd870c9fa..7afc083451 100644 --- a/share/shwap/p2p/bitswap/block.go +++ b/share/shwap/p2p/bitswap/block.go @@ -1,10 +1,15 @@ package bitswap import ( + "fmt" + + "github.com/gogo/protobuf/proto" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" logger "github.com/ipfs/go-log/v2" + bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" + "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" @@ -23,6 +28,7 @@ type Block interface { // CID returns Shwap ID of the Block formatted as CID. CID() cid.Cid // BlockFromEDS extract Bitswap Block out of the EDS. + // TODO: Split into MarshalBinary and Populate BlockFromEDS(*rsmt2d.ExtendedDataSquare) (blocks.Block, error) // IsEmpty reports whether the Block been populated with Shwap container. @@ -32,3 +38,28 @@ type Block interface { // Population involves data validation against the Root. PopulateFn(*share.Root) PopulateFn } + +// toBlock converts given protobuf container into Bitswap Block. +func toBlock(cid cid.Cid, container proto.Marshaler) (blocks.Block, error) { + containerData, err := container.Marshal() + if err != nil { + return nil, fmt.Errorf("marshaling container: %w", err) + } + + blkProto := bitswappb.Block{ + Cid: cid.Bytes(), + Container: containerData, + } + + blkData, err := blkProto.Marshal() + if err != nil { + return nil, fmt.Errorf("marshaling Block protobuf: %w", err) + } + + blk, err := blocks.NewBlockWithCid(blkData, cid) + if err != nil { + return nil, fmt.Errorf("assembling Bitswap block: %w", err) + } + + return blk, nil +} diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 758e535278..2c18adffd6 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -9,6 +9,8 @@ import ( "github.com/ipfs/boxo/exchange" "github.com/ipfs/go-cid" + bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" + "github.com/celestiaorg/celestia-node/share" ) @@ -47,7 +49,8 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks } // uncommon duplicate case: concurrent fetching of the same block, // so we have to populate it ourselves instead of the hasher, - err := blk.PopulateFn(root)(bitswapBlk.RawData()) + populateFn := blk.PopulateFn(root) + _, err := populate(populateFn, bitswapBlk.RawData()) if err != nil { // this means verification succeeded in the hasher but failed here // this case should never happen in practice @@ -63,6 +66,42 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks return ctx.Err() } +// populate populates the data into a Block via PopulateFn +// If populateFn is nil -- gets it from the global populatorFns. +func populate(populate PopulateFn, data []byte) ([]byte, error) { + var blk bitswappb.Block + err := blk.Unmarshal(data) + if err != nil { + return nil, fmt.Errorf("unmarshalling block: %w", err) + } + cid, err := cid.Cast(blk.Cid) + if err != nil { + return nil, fmt.Errorf("casting cid: %w", err) + } + // get ID out of CID validating it + id, err := extractCID(cid) + if err != nil { + return nil, fmt.Errorf("validating cid: %w", err) + } + + if populate == nil { + // get registered PopulateFn and use it to check data validity and + // pass it to Fetch caller + val, ok := populatorFns.LoadAndDelete(cid) + if !ok { + return nil, fmt.Errorf("no populator registered") + } + populate = val.(PopulateFn) + } + + err = populate(blk.Container) + if err != nil { + return nil, fmt.Errorf("verifying data: %w", err) + } + + return id, nil +} + // populatorFns exist to communicate between Fetch and hasher. // // Fetch registers PopulateFNs that hasher then uses to validate and populate Block responses coming @@ -86,41 +125,9 @@ type hasher struct { } func (h *hasher) Write(data []byte) (int, error) { - if len(data) == 0 { - errMsg := "hasher: empty message" - log.Error(errMsg) - return 0, fmt.Errorf("shwap/bitswap: %s", errMsg) - } - - // cut off the first tag type byte out of protobuf data - const pbTypeOffset = 1 - cidData := data[pbTypeOffset:] - - cid, err := readCID(cidData) - if err != nil { - err = fmt.Errorf("hasher: reading cid: %w", err) - log.Error(err) - return 0, fmt.Errorf("shwap/bitswap: %w", err) - } - // get ID out of CID and validate it - id, err := extractCID(cid) - if err != nil { - err = fmt.Errorf("hasher: validating cid: %w", err) - log.Error(err) - return 0, fmt.Errorf("shwap/bitswap: %w", err) - } - // get registered PopulateFn and use it to check data validity and - // pass it to Fetch caller - val, ok := populatorFns.LoadAndDelete(cid) - if !ok { - errMsg := "hasher: no verifier registered" - log.Error(errMsg) - return 0, fmt.Errorf("shwap/bitswap: %s", errMsg) - } - populate := val.(PopulateFn) - err = populate(data) + id, err := populate(nil, data) if err != nil { - err = fmt.Errorf("hasher: verifying data: %w", err) + err = fmt.Errorf("hasher: %w", err) log.Error(err) return 0, fmt.Errorf("shwap/bitswap: %w", err) } diff --git a/share/shwap/p2p/bitswap/cid.go b/share/shwap/p2p/bitswap/cid.go index bc72755854..53129393da 100644 --- a/share/shwap/p2p/bitswap/cid.go +++ b/share/shwap/p2p/bitswap/cid.go @@ -2,7 +2,6 @@ package bitswap import ( "encoding" - "encoding/binary" "fmt" "github.com/ipfs/go-cid" @@ -23,25 +22,6 @@ func (a allowlist) IsAllowed(code uint64) bool { return ok } -// readCID reads out cid out of bytes -func readCID(data []byte) (cid.Cid, error) { - cidLen, ln := binary.Uvarint(data) - if ln <= 0 || len(data) < ln+int(cidLen) { - return cid.Undef, fmt.Errorf("invalid data length") - } - // extract CID out of data - // we do this on the raw data to: - // * Avoid complicating hasher with generalized bytes -> type unmarshalling - // * Avoid type allocations - cidRaw := data[ln : ln+int(cidLen)] - castCid, err := cid.Cast(cidRaw) - if err != nil { - return cid.Undef, fmt.Errorf("casting cid: %w", err) - } - - return castCid, nil -} - // extractCID retrieves Shwap ID out of the CID. func extractCID(cid cid.Cid) ([]byte, error) { if err := validateCID(cid); err != nil { diff --git a/share/shwap/p2p/bitswap/pb/bitswap.pb.go b/share/shwap/p2p/bitswap/pb/bitswap.pb.go index d159661361..a84077e9ef 100644 --- a/share/shwap/p2p/bitswap/pb/bitswap.pb.go +++ b/share/shwap/p2p/bitswap/pb/bitswap.pb.go @@ -5,7 +5,6 @@ package pb import ( fmt "fmt" - pb "github.com/celestiaorg/celestia-node/share/shwap/pb" proto "github.com/gogo/protobuf/proto" io "io" math "math" @@ -23,23 +22,23 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -type RowBlock struct { - RowCid []byte `protobuf:"bytes,1,opt,name=row_cid,json=rowCid,proto3" json:"row_cid,omitempty"` - Row *pb.Row `protobuf:"bytes,2,opt,name=row,proto3" json:"row,omitempty"` +type Block struct { + Cid []byte `protobuf:"bytes,1,opt,name=cid,proto3" json:"cid,omitempty"` + Container []byte `protobuf:"bytes,2,opt,name=container,proto3" json:"container,omitempty"` } -func (m *RowBlock) Reset() { *m = RowBlock{} } -func (m *RowBlock) String() string { return proto.CompactTextString(m) } -func (*RowBlock) ProtoMessage() {} -func (*RowBlock) Descriptor() ([]byte, []int) { +func (m *Block) Reset() { *m = Block{} } +func (m *Block) String() string { return proto.CompactTextString(m) } +func (*Block) ProtoMessage() {} +func (*Block) Descriptor() ([]byte, []int) { return fileDescriptor_09fd4e2ff1d5ce94, []int{0} } -func (m *RowBlock) XXX_Unmarshal(b []byte) error { +func (m *Block) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *RowBlock) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *Block) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_RowBlock.Marshal(b, m, deterministic) + return xxx_messageInfo_Block.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -49,140 +48,34 @@ func (m *RowBlock) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return b[:n], nil } } -func (m *RowBlock) XXX_Merge(src proto.Message) { - xxx_messageInfo_RowBlock.Merge(m, src) +func (m *Block) XXX_Merge(src proto.Message) { + xxx_messageInfo_Block.Merge(m, src) } -func (m *RowBlock) XXX_Size() int { +func (m *Block) XXX_Size() int { return m.Size() } -func (m *RowBlock) XXX_DiscardUnknown() { - xxx_messageInfo_RowBlock.DiscardUnknown(m) +func (m *Block) XXX_DiscardUnknown() { + xxx_messageInfo_Block.DiscardUnknown(m) } -var xxx_messageInfo_RowBlock proto.InternalMessageInfo +var xxx_messageInfo_Block proto.InternalMessageInfo -func (m *RowBlock) GetRowCid() []byte { +func (m *Block) GetCid() []byte { if m != nil { - return m.RowCid + return m.Cid } return nil } -func (m *RowBlock) GetRow() *pb.Row { +func (m *Block) GetContainer() []byte { if m != nil { - return m.Row - } - return nil -} - -type SampleBlock struct { - SampleCid []byte `protobuf:"bytes,1,opt,name=sample_cid,json=sampleCid,proto3" json:"sample_cid,omitempty"` - Sample *pb.Sample `protobuf:"bytes,2,opt,name=sample,proto3" json:"sample,omitempty"` -} - -func (m *SampleBlock) Reset() { *m = SampleBlock{} } -func (m *SampleBlock) String() string { return proto.CompactTextString(m) } -func (*SampleBlock) ProtoMessage() {} -func (*SampleBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_09fd4e2ff1d5ce94, []int{1} -} -func (m *SampleBlock) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *SampleBlock) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_SampleBlock.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *SampleBlock) XXX_Merge(src proto.Message) { - xxx_messageInfo_SampleBlock.Merge(m, src) -} -func (m *SampleBlock) XXX_Size() int { - return m.Size() -} -func (m *SampleBlock) XXX_DiscardUnknown() { - xxx_messageInfo_SampleBlock.DiscardUnknown(m) -} - -var xxx_messageInfo_SampleBlock proto.InternalMessageInfo - -func (m *SampleBlock) GetSampleCid() []byte { - if m != nil { - return m.SampleCid - } - return nil -} - -func (m *SampleBlock) GetSample() *pb.Sample { - if m != nil { - return m.Sample - } - return nil -} - -type RowNamespaceDataBlock struct { - RowNamespaceDataCid []byte `protobuf:"bytes,1,opt,name=row_namespace_data_cid,json=rowNamespaceDataCid,proto3" json:"row_namespace_data_cid,omitempty"` - Data *pb.RowNamespaceData `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` -} - -func (m *RowNamespaceDataBlock) Reset() { *m = RowNamespaceDataBlock{} } -func (m *RowNamespaceDataBlock) String() string { return proto.CompactTextString(m) } -func (*RowNamespaceDataBlock) ProtoMessage() {} -func (*RowNamespaceDataBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_09fd4e2ff1d5ce94, []int{2} -} -func (m *RowNamespaceDataBlock) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *RowNamespaceDataBlock) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_RowNamespaceDataBlock.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *RowNamespaceDataBlock) XXX_Merge(src proto.Message) { - xxx_messageInfo_RowNamespaceDataBlock.Merge(m, src) -} -func (m *RowNamespaceDataBlock) XXX_Size() int { - return m.Size() -} -func (m *RowNamespaceDataBlock) XXX_DiscardUnknown() { - xxx_messageInfo_RowNamespaceDataBlock.DiscardUnknown(m) -} - -var xxx_messageInfo_RowNamespaceDataBlock proto.InternalMessageInfo - -func (m *RowNamespaceDataBlock) GetRowNamespaceDataCid() []byte { - if m != nil { - return m.RowNamespaceDataCid - } - return nil -} - -func (m *RowNamespaceDataBlock) GetData() *pb.RowNamespaceData { - if m != nil { - return m.Data + return m.Container } return nil } func init() { - proto.RegisterType((*RowBlock)(nil), "bitswap.RowBlock") - proto.RegisterType((*SampleBlock)(nil), "bitswap.SampleBlock") - proto.RegisterType((*RowNamespaceDataBlock)(nil), "bitswap.RowNamespaceDataBlock") + proto.RegisterType((*Block)(nil), "bitswap.Block") } func init() { @@ -190,29 +83,21 @@ func init() { } var fileDescriptor_09fd4e2ff1d5ce94 = []byte{ - // 296 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xc1, 0x4a, 0xc3, 0x40, - 0x10, 0x86, 0xbb, 0x2a, 0xad, 0x4e, 0xf5, 0x52, 0xd1, 0x96, 0xa2, 0x4b, 0x29, 0x08, 0x05, 0xb1, - 0x81, 0xf6, 0x01, 0xc4, 0xea, 0xd9, 0xc3, 0xf6, 0xa4, 0x97, 0xb2, 0x49, 0x96, 0x26, 0x98, 0x74, - 0x96, 0xdd, 0x95, 0xc5, 0xb7, 0xf0, 0xb1, 0x3c, 0xf6, 0xe8, 0x51, 0x92, 0x17, 0x91, 0xcd, 0xa6, - 0xda, 0x0a, 0xde, 0xfe, 0x99, 0xf9, 0xe7, 0xfb, 0x93, 0x59, 0x18, 0xe9, 0x84, 0x2b, 0x11, 0xe8, - 0xc4, 0x72, 0x19, 0xc8, 0x89, 0x0c, 0xc2, 0xd4, 0xe8, 0x4a, 0x87, 0x1b, 0x39, 0x96, 0x0a, 0x0d, - 0x76, 0x5a, 0x75, 0xd9, 0xef, 0xef, 0xac, 0x84, 0x5e, 0x78, 0xd3, 0xf0, 0x0e, 0x0e, 0x19, 0xda, - 0x59, 0x86, 0xd1, 0x4b, 0xa7, 0x0b, 0x2d, 0x85, 0x76, 0x11, 0xa5, 0x71, 0x8f, 0x0c, 0xc8, 0xe8, - 0x98, 0x35, 0x15, 0xda, 0xfb, 0x34, 0xee, 0x5c, 0xc0, 0xbe, 0x42, 0xdb, 0xdb, 0x1b, 0x90, 0x51, - 0x7b, 0x02, 0x63, 0xbf, 0xcf, 0xd0, 0x32, 0xd7, 0x1e, 0xce, 0xa1, 0x3d, 0xe7, 0xb9, 0xcc, 0x84, - 0xa7, 0x5c, 0x02, 0xe8, 0xaa, 0xdc, 0x02, 0x1d, 0xf9, 0x8e, 0x63, 0x5d, 0x41, 0xd3, 0x17, 0x35, - 0xee, 0xa4, 0xc6, 0x79, 0x04, 0xab, 0x87, 0xc3, 0x37, 0x38, 0x63, 0x68, 0x1f, 0x79, 0x2e, 0xb4, - 0xe4, 0x91, 0x78, 0xe0, 0x86, 0x7b, 0xfc, 0x14, 0xce, 0xdd, 0x47, 0xae, 0x36, 0x93, 0x45, 0xcc, - 0x0d, 0xdf, 0x8a, 0x3a, 0x55, 0x7f, 0xd6, 0x5c, 0xe8, 0x35, 0x1c, 0x38, 0x5b, 0x1d, 0xd9, 0xfd, - 0xfd, 0x83, 0x1d, 0x27, 0xab, 0x4c, 0xb3, 0xa7, 0x8f, 0x82, 0x92, 0x75, 0x41, 0xc9, 0x57, 0x41, - 0xc9, 0x7b, 0x49, 0x1b, 0xeb, 0x92, 0x36, 0x3e, 0x4b, 0xda, 0x78, 0xbe, 0x5d, 0xa6, 0x26, 0x79, - 0x0d, 0xc7, 0x11, 0xe6, 0x41, 0x24, 0x32, 0xa1, 0x4d, 0xca, 0x51, 0x2d, 0x7f, 0xf4, 0xcd, 0x0a, - 0x63, 0x77, 0xe7, 0xff, 0x1e, 0x28, 0x6c, 0x56, 0x47, 0x9f, 0x7e, 0x07, 0x00, 0x00, 0xff, 0xff, - 0x7d, 0xd6, 0xa6, 0x3b, 0xc5, 0x01, 0x00, 0x00, -} - -func (m *RowBlock) Marshal() (dAtA []byte, err error) { + // 171 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x28, 0xce, 0x48, 0x2c, + 0x4a, 0xd5, 0x2f, 0xce, 0x28, 0x4f, 0x2c, 0xd0, 0x2f, 0x30, 0x2a, 0xd0, 0x4f, 0xca, 0x2c, 0x29, + 0x06, 0xb3, 0x93, 0x60, 0x4c, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0x76, 0x28, 0x57, 0xc9, + 0x9c, 0x8b, 0xd5, 0x29, 0x27, 0x3f, 0x39, 0x5b, 0x48, 0x80, 0x8b, 0x39, 0x39, 0x33, 0x45, 0x82, + 0x51, 0x81, 0x51, 0x83, 0x27, 0x08, 0xc4, 0x14, 0x92, 0xe1, 0xe2, 0x4c, 0xce, 0xcf, 0x2b, 0x49, + 0xcc, 0xcc, 0x4b, 0x2d, 0x92, 0x60, 0x02, 0x8b, 0x23, 0x04, 0x9c, 0x22, 0x4f, 0x3c, 0x92, 0x63, + 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, + 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x3e, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, + 0x3f, 0x57, 0x3f, 0x39, 0x35, 0x27, 0xb5, 0xb8, 0x24, 0x33, 0x31, 0xbf, 0x28, 0x1d, 0xce, 0xd6, + 0xcd, 0xcb, 0x4f, 0x01, 0x39, 0x12, 0x97, 0x53, 0x93, 0xd8, 0xc0, 0x6e, 0x34, 0x06, 0x04, 0x00, + 0x00, 0xff, 0xff, 0xe7, 0x9c, 0x32, 0xc5, 0xcf, 0x00, 0x00, 0x00, +} + +func (m *Block) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -222,116 +107,27 @@ func (m *RowBlock) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *RowBlock) MarshalTo(dAtA []byte) (int, error) { +func (m *Block) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *RowBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *Block) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.Row != nil { - { - size, err := m.Row.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintBitswap(dAtA, i, uint64(size)) - } + if len(m.Container) > 0 { + i -= len(m.Container) + copy(dAtA[i:], m.Container) + i = encodeVarintBitswap(dAtA, i, uint64(len(m.Container))) i-- dAtA[i] = 0x12 } - if len(m.RowCid) > 0 { - i -= len(m.RowCid) - copy(dAtA[i:], m.RowCid) - i = encodeVarintBitswap(dAtA, i, uint64(len(m.RowCid))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *SampleBlock) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *SampleBlock) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *SampleBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Sample != nil { - { - size, err := m.Sample.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintBitswap(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if len(m.SampleCid) > 0 { - i -= len(m.SampleCid) - copy(dAtA[i:], m.SampleCid) - i = encodeVarintBitswap(dAtA, i, uint64(len(m.SampleCid))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *RowNamespaceDataBlock) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *RowNamespaceDataBlock) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *RowNamespaceDataBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Data != nil { - { - size, err := m.Data.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintBitswap(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if len(m.RowNamespaceDataCid) > 0 { - i -= len(m.RowNamespaceDataCid) - copy(dAtA[i:], m.RowNamespaceDataCid) - i = encodeVarintBitswap(dAtA, i, uint64(len(m.RowNamespaceDataCid))) + if len(m.Cid) > 0 { + i -= len(m.Cid) + copy(dAtA[i:], m.Cid) + i = encodeVarintBitswap(dAtA, i, uint64(len(m.Cid))) i-- dAtA[i] = 0xa } @@ -349,54 +145,20 @@ func encodeVarintBitswap(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *RowBlock) Size() (n int) { +func (m *Block) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.RowCid) + l = len(m.Cid) if l > 0 { n += 1 + l + sovBitswap(uint64(l)) } - if m.Row != nil { - l = m.Row.Size() - n += 1 + l + sovBitswap(uint64(l)) - } - return n -} - -func (m *SampleBlock) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.SampleCid) + l = len(m.Container) if l > 0 { n += 1 + l + sovBitswap(uint64(l)) } - if m.Sample != nil { - l = m.Sample.Size() - n += 1 + l + sovBitswap(uint64(l)) - } - return n -} - -func (m *RowNamespaceDataBlock) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.RowNamespaceDataCid) - if l > 0 { - n += 1 + l + sovBitswap(uint64(l)) - } - if m.Data != nil { - l = m.Data.Size() - n += 1 + l + sovBitswap(uint64(l)) - } return n } @@ -406,127 +168,7 @@ func sovBitswap(x uint64) (n int) { func sozBitswap(x uint64) (n int) { return sovBitswap(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *RowBlock) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBitswap - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RowBlock: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RowBlock: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RowCid", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBitswap - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBitswap - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthBitswap - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.RowCid = append(m.RowCid[:0], dAtA[iNdEx:postIndex]...) - if m.RowCid == nil { - m.RowCid = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Row", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBitswap - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBitswap - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthBitswap - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Row == nil { - m.Row = &pb.Row{} - } - if err := m.Row.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBitswap(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthBitswap - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *SampleBlock) Unmarshal(dAtA []byte) error { +func (m *Block) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -549,15 +191,15 @@ func (m *SampleBlock) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: SampleBlock: wiretype end group for non-group") + return fmt.Errorf("proto: Block: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: SampleBlock: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Block: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SampleCid", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Cid", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -584,100 +226,14 @@ func (m *SampleBlock) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.SampleCid = append(m.SampleCid[:0], dAtA[iNdEx:postIndex]...) - if m.SampleCid == nil { - m.SampleCid = []byte{} + m.Cid = append(m.Cid[:0], dAtA[iNdEx:postIndex]...) + if m.Cid == nil { + m.Cid = []byte{} } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sample", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBitswap - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBitswap - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthBitswap - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Sample == nil { - m.Sample = &pb.Sample{} - } - if err := m.Sample.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBitswap(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthBitswap - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *RowNamespaceDataBlock) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBitswap - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RowNamespaceDataBlock: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RowNamespaceDataBlock: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RowNamespaceDataCid", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Container", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -704,45 +260,9 @@ func (m *RowNamespaceDataBlock) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.RowNamespaceDataCid = append(m.RowNamespaceDataCid[:0], dAtA[iNdEx:postIndex]...) - if m.RowNamespaceDataCid == nil { - m.RowNamespaceDataCid = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBitswap - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBitswap - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthBitswap - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Data == nil { - m.Data = &pb.RowNamespaceData{} - } - if err := m.Data.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.Container = append(m.Container[:0], dAtA[iNdEx:postIndex]...) + if m.Container == nil { + m.Container = []byte{} } iNdEx = postIndex default: diff --git a/share/shwap/p2p/bitswap/pb/bitswap.proto b/share/shwap/p2p/bitswap/pb/bitswap.proto index 8f2fb78748..aa06fbf5b5 100644 --- a/share/shwap/p2p/bitswap/pb/bitswap.proto +++ b/share/shwap/p2p/bitswap/pb/bitswap.proto @@ -2,19 +2,7 @@ syntax = "proto3"; package bitswap; option go_package = "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb"; -import "share/shwap/pb/shwap.proto"; - -message RowBlock { - bytes row_cid = 1; - shwap.Row row = 2; -} - -message SampleBlock { - bytes sample_cid = 1; - shwap.Sample sample = 2; -} - -message RowNamespaceDataBlock { - bytes row_namespace_data_cid = 1; - shwap.RowNamespaceData data = 2; +message Block { + bytes cid = 1; + bytes container = 2; } diff --git a/share/shwap/p2p/bitswap/row_block.go b/share/shwap/p2p/bitswap/row_block.go index b6eb37c8d3..60f03f0eda 100644 --- a/share/shwap/p2p/bitswap/row_block.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -11,7 +11,7 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/shwap" - bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" + shwappb "github.com/celestiaorg/celestia-node/share/shwap/pb" ) const ( @@ -70,21 +70,9 @@ func (rb *RowBlock) CID() cid.Cid { func (rb *RowBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { row := shwap.RowFromEDS(eds, rb.ID.RowIndex, shwap.Left) - - cid := rb.CID() - rowBlk := bitswappb.RowBlock{ - RowCid: cid.Bytes(), - Row: row.ToProto(), - } - - blkData, err := rowBlk.Marshal() + blk, err := toBlock(rb.CID(), row.ToProto()) if err != nil { - return nil, fmt.Errorf("marshaling RowBlock: %w", err) - } - - blk, err := blocks.NewBlockWithCid(blkData, cid) - if err != nil { - return nil, fmt.Errorf("assembling Bitswap block: %w", err) + return nil, fmt.Errorf("converting Row to Bitswap block: %w", err) } return blk, nil @@ -103,18 +91,16 @@ func (rb *RowBlock) PopulateFn(root *share.Root) PopulateFn { if !rb.IsEmpty() { return nil } - var rowBlk bitswappb.RowBlock - if err := rowBlk.Unmarshal(data); err != nil { - return fmt.Errorf("unmarshaling RowBlock: %w", err) + var row shwappb.Row + if err := row.Unmarshal(data); err != nil { + return fmt.Errorf("unmarshaling Row: %w", err) } - cntr := shwap.RowFromProto(rowBlk.Row) + cntr := shwap.RowFromProto(&row) if err := cntr.Validate(root, rb.ID.RowIndex); err != nil { return fmt.Errorf("validating Row: %w", err) } rb.container.Store(&cntr) - - // NOTE: We don't have to validate the ID here, as it is verified in the hasher. return nil } } diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go index ebd5300c98..79d5be0191 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -7,11 +7,12 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + shwappb "github.com/celestiaorg/celestia-node/share/shwap/pb" + "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/shwap" - bitswapb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" ) const ( @@ -80,20 +81,9 @@ func (rndb *RowNamespaceDataBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) return nil, err } - cid := rndb.CID() - rndBlk := bitswapb.RowNamespaceDataBlock{ - RowNamespaceDataCid: cid.Bytes(), - Data: rnd.ToProto(), - } - - blkData, err := rndBlk.Marshal() + blk, err := toBlock(rndb.CID(), rnd.ToProto()) if err != nil { - return nil, fmt.Errorf("marshaling RowNamespaceDataBlock: %w", err) - } - - blk, err := blocks.NewBlockWithCid(blkData, cid) - if err != nil { - return nil, fmt.Errorf("assembling Bitswap block: %w", err) + return nil, fmt.Errorf("converting RowNamespaceData to Bitswap block: %w", err) } return blk, nil @@ -112,18 +102,16 @@ func (rndb *RowNamespaceDataBlock) PopulateFn(root *share.Root) PopulateFn { if !rndb.IsEmpty() { return nil } - var rndBlk bitswapb.RowNamespaceDataBlock - if err := rndBlk.Unmarshal(data); err != nil { - return fmt.Errorf("unmarshaling RowNamespaceDataBlock: %w", err) + var rnd shwappb.RowNamespaceData + if err := rnd.Unmarshal(data); err != nil { + return fmt.Errorf("unmarshaling RowNamespaceData: %w", err) } - cntr := shwap.RowNamespaceDataFromProto(rndBlk.Data) + cntr := shwap.RowNamespaceDataFromProto(&rnd) if err := cntr.Validate(root, rndb.ID.DataNamespace, rndb.ID.RowIndex); err != nil { return fmt.Errorf("validating RowNamespaceData: %w", err) } rndb.container.Store(&cntr) - - // NOTE: We don't have to validate the ID here, as it is verified in the hasher. return nil } } diff --git a/share/shwap/p2p/bitswap/sample_block.go b/share/shwap/p2p/bitswap/sample_block.go index ae98c58bc1..a9c574edba 100644 --- a/share/shwap/p2p/bitswap/sample_block.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -7,11 +7,12 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + shwappb "github.com/celestiaorg/celestia-node/share/shwap/pb" + "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/shwap" - bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" ) const ( @@ -75,20 +76,9 @@ func (sb *SampleBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Bloc return nil, err } - cid := sb.CID() - smplBlk := bitswappb.SampleBlock{ - SampleCid: cid.Bytes(), - Sample: smpl.ToProto(), - } - - blkData, err := smplBlk.Marshal() + blk, err := toBlock(sb.CID(), smpl.ToProto()) if err != nil { - return nil, fmt.Errorf("marshaling SampleBlock: %w", err) - } - - blk, err := blocks.NewBlockWithCid(blkData, cid) - if err != nil { - return nil, fmt.Errorf("assembling Bitswap block: %w", err) + return nil, fmt.Errorf("converting Sample to Bitswap block: %w", err) } return blk, nil @@ -107,18 +97,16 @@ func (sb *SampleBlock) PopulateFn(root *share.Root) PopulateFn { if !sb.IsEmpty() { return nil } - var sampleBlk bitswappb.SampleBlock - if err := sampleBlk.Unmarshal(data); err != nil { - return fmt.Errorf("unmarshaling SampleBlock: %w", err) + var sample shwappb.Sample + if err := sample.Unmarshal(data); err != nil { + return fmt.Errorf("unmarshaling Sample: %w", err) } - cntr := shwap.SampleFromProto(sampleBlk.Sample) + cntr := shwap.SampleFromProto(&sample) if err := cntr.Validate(root, sb.ID.RowIndex, sb.ID.ShareIndex); err != nil { return fmt.Errorf("validating Sample: %w", err) } sb.container.Store(&cntr) - - // NOTE: We don't have to validate the ID here, as it is verified in the hasher. return nil } } From 01439efd0b20c298b79f96aea4ef0ee41a7dd10f Mon Sep 17 00:00:00 2001 From: Wondertan Date: Wed, 12 Jun 2024 19:36:50 +0200 Subject: [PATCH 17/40] migrate to Accessor --- share/shwap/p2p/bitswap/block.go | 7 ++++--- share/shwap/p2p/bitswap/block_fetch_test.go | 12 +++++++----- share/shwap/p2p/bitswap/row_block.go | 13 ++++++++++--- share/shwap/p2p/bitswap/row_namespace_data_block.go | 11 ++++++----- share/shwap/p2p/bitswap/sample_block.go | 11 ++++++----- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/share/shwap/p2p/bitswap/block.go b/share/shwap/p2p/bitswap/block.go index 7afc083451..4a293876ba 100644 --- a/share/shwap/p2p/bitswap/block.go +++ b/share/shwap/p2p/bitswap/block.go @@ -1,6 +1,7 @@ package bitswap import ( + "context" "fmt" "github.com/gogo/protobuf/proto" @@ -8,9 +9,9 @@ import ( "github.com/ipfs/go-cid" logger "github.com/ipfs/go-log/v2" - bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" + eds "github.com/celestiaorg/celestia-node/share/new_eds" - "github.com/celestiaorg/rsmt2d" + bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" "github.com/celestiaorg/celestia-node/share" ) @@ -29,7 +30,7 @@ type Block interface { CID() cid.Cid // BlockFromEDS extract Bitswap Block out of the EDS. // TODO: Split into MarshalBinary and Populate - BlockFromEDS(*rsmt2d.ExtendedDataSquare) (blocks.Block, error) + BlockFromEDS(context.Context, eds.Accessor) (blocks.Block, error) // IsEmpty reports whether the Block been populated with Shwap container. // If the Block is empty, it can be populated with Fetch. diff --git a/share/shwap/p2p/bitswap/block_fetch_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go index fa1594499d..0a27475e64 100644 --- a/share/shwap/p2p/bitswap/block_fetch_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -23,6 +23,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + eds "github.com/celestiaorg/celestia-node/share/new_eds" + "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" @@ -102,14 +104,14 @@ func fetcher(ctx context.Context, t *testing.T, bstore blockstore.Blockstore) ex } type testBlockstore struct { - eds *rsmt2d.ExtendedDataSquare + eds eds.Accessor } -func newTestBlockstore(eds *rsmt2d.ExtendedDataSquare) *testBlockstore { - return &testBlockstore{eds: eds} +func newTestBlockstore(rsmt2sEds *rsmt2d.ExtendedDataSquare) *testBlockstore { + return &testBlockstore{eds: eds.Rsmt2D{ExtendedDataSquare: rsmt2sEds}} } -func (b *testBlockstore) Get(_ context.Context, cid cid.Cid) (blocks.Block, error) { +func (b *testBlockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { spec, ok := specRegistry[cid.Prefix().MhType] if !ok { return nil, fmt.Errorf("unsupported codec") @@ -120,7 +122,7 @@ func (b *testBlockstore) Get(_ context.Context, cid cid.Cid) (blocks.Block, erro return nil, err } - return bldr.BlockFromEDS(b.eds) + return bldr.BlockFromEDS(ctx, b.eds) } func (b *testBlockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { diff --git a/share/shwap/p2p/bitswap/row_block.go b/share/shwap/p2p/bitswap/row_block.go index 60f03f0eda..f334229634 100644 --- a/share/shwap/p2p/bitswap/row_block.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -1,12 +1,15 @@ package bitswap import ( + "context" "fmt" "sync/atomic" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + eds "github.com/celestiaorg/celestia-node/share/new_eds" + "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" @@ -68,9 +71,13 @@ func (rb *RowBlock) CID() cid.Cid { return encodeCID(rb.ID, rowMultihashCode, rowCodec) } -func (rb *RowBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { - row := shwap.RowFromEDS(eds, rb.ID.RowIndex, shwap.Left) - blk, err := toBlock(rb.CID(), row.ToProto()) +func (rb *RowBlock) BlockFromEDS(ctx context.Context, eds eds.Accessor) (blocks.Block, error) { + half, err := eds.AxisHalf(ctx, rsmt2d.Row, rb.ID.RowIndex) + if err != nil { + return nil, fmt.Errorf("getting Row AxisHalf: %w", err) + } + + blk, err := toBlock(rb.CID(), half.ToRow().ToProto()) if err != nil { return nil, fmt.Errorf("converting Row to Bitswap block: %w", err) } diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go index 79d5be0191..fa71bae863 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -1,15 +1,16 @@ package bitswap import ( + "context" "fmt" "sync/atomic" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - shwappb "github.com/celestiaorg/celestia-node/share/shwap/pb" + eds "github.com/celestiaorg/celestia-node/share/new_eds" - "github.com/celestiaorg/rsmt2d" + shwappb "github.com/celestiaorg/celestia-node/share/shwap/pb" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/shwap" @@ -75,10 +76,10 @@ func (rndb *RowNamespaceDataBlock) CID() cid.Cid { return encodeCID(rndb.ID, rowNamespaceDataMultihashCode, rowNamespaceDataCodec) } -func (rndb *RowNamespaceDataBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { - rnd, err := shwap.RowNamespaceDataFromEDS(eds, rndb.ID.DataNamespace, rndb.ID.RowIndex) +func (rndb *RowNamespaceDataBlock) BlockFromEDS(ctx context.Context, eds eds.Accessor) (blocks.Block, error) { + rnd, err := eds.RowNamespaceData(ctx, rndb.ID.DataNamespace, rndb.ID.RowIndex) if err != nil { - return nil, err + return nil, fmt.Errorf("getting RowNamespaceData: %w", err) } blk, err := toBlock(rndb.CID(), rnd.ToProto()) diff --git a/share/shwap/p2p/bitswap/sample_block.go b/share/shwap/p2p/bitswap/sample_block.go index a9c574edba..9192441e54 100644 --- a/share/shwap/p2p/bitswap/sample_block.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -1,15 +1,16 @@ package bitswap import ( + "context" "fmt" "sync/atomic" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - shwappb "github.com/celestiaorg/celestia-node/share/shwap/pb" + eds "github.com/celestiaorg/celestia-node/share/new_eds" - "github.com/celestiaorg/rsmt2d" + shwappb "github.com/celestiaorg/celestia-node/share/shwap/pb" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/shwap" @@ -70,10 +71,10 @@ func (sb *SampleBlock) CID() cid.Cid { return encodeCID(sb.ID, sampleMultihashCode, sampleCodec) } -func (sb *SampleBlock) BlockFromEDS(eds *rsmt2d.ExtendedDataSquare) (blocks.Block, error) { - smpl, err := shwap.SampleFromEDS(eds, rsmt2d.Row, sb.ID.RowIndex, sb.ID.ShareIndex) +func (sb *SampleBlock) BlockFromEDS(ctx context.Context, eds eds.Accessor) (blocks.Block, error) { + smpl, err := eds.Sample(ctx, sb.ID.RowIndex, sb.ID.ShareIndex) if err != nil { - return nil, err + return nil, fmt.Errorf("getting Sample: %w", err) } blk, err := toBlock(sb.CID(), smpl.ToProto()) From 5af530046e51572bcd4f174387ce8f3e3d8a1d3b Mon Sep 17 00:00:00 2001 From: Wondertan Date: Wed, 12 Jun 2024 20:14:43 +0200 Subject: [PATCH 18/40] extract blockstore --- share/shwap/p2p/bitswap/block.go | 3 + share/shwap/p2p/bitswap/block_fetch.go | 4 +- share/shwap/p2p/bitswap/block_fetch_test.go | 71 ++++--------------- share/shwap/p2p/bitswap/block_store.go | 70 ++++++++++++++++++ share/shwap/p2p/bitswap/row_block.go | 4 ++ share/shwap/p2p/bitswap/row_block_test.go | 2 +- .../p2p/bitswap/row_namespace_data_block.go | 4 ++ .../bitswap/row_namespace_data_block_test.go | 2 +- share/shwap/p2p/bitswap/sample_block.go | 4 ++ share/shwap/p2p/bitswap/sample_block_test.go | 2 +- 10 files changed, 102 insertions(+), 64 deletions(-) create mode 100644 share/shwap/p2p/bitswap/block_store.go diff --git a/share/shwap/p2p/bitswap/block.go b/share/shwap/p2p/bitswap/block.go index 4a293876ba..079097023b 100644 --- a/share/shwap/p2p/bitswap/block.go +++ b/share/shwap/p2p/bitswap/block.go @@ -25,9 +25,12 @@ type PopulateFn func([]byte) error // Block represents Bitswap compatible Shwap container. // All Shwap containers must have a registerBlock-ed wrapper // implementing the interface to be compatible with Bitswap. +// NOTE: This is not Blockchain block, but IPFS/Bitswap block/ type Block interface { // CID returns Shwap ID of the Block formatted as CID. CID() cid.Cid + // Height reports the Height the Shwap Container data behind the Block is from. + Height() uint64 // BlockFromEDS extract Bitswap Block out of the EDS. // TODO: Split into MarshalBinary and Populate BlockFromEDS(context.Context, eds.Accessor) (blocks.Block, error) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 2c18adffd6..bed918451e 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -67,7 +67,7 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks } // populate populates the data into a Block via PopulateFn -// If populateFn is nil -- gets it from the global populatorFns. +// If populate is nil -- gets it from the global populatorFns. func populate(populate PopulateFn, data []byte) ([]byte, error) { var blk bitswappb.Block err := blk.Unmarshal(data) @@ -89,7 +89,7 @@ func populate(populate PopulateFn, data []byte) ([]byte, error) { // pass it to Fetch caller val, ok := populatorFns.LoadAndDelete(cid) if !ok { - return nil, fmt.Errorf("no populator registered") + return nil, fmt.Errorf("no populator registered for %s", cid.String()) } populate = val.(PopulateFn) } diff --git a/share/shwap/p2p/bitswap/block_fetch_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go index 0a27475e64..85d4bad0c7 100644 --- a/share/shwap/p2p/bitswap/block_fetch_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -2,7 +2,6 @@ package bitswap import ( "context" - "fmt" "math/rand/v2" "runtime" "sync" @@ -14,8 +13,6 @@ import ( "github.com/ipfs/boxo/blockstore" "github.com/ipfs/boxo/exchange" "github.com/ipfs/boxo/routing/offline" - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" record "github.com/libp2p/go-libp2p-record" @@ -39,7 +36,7 @@ func TestFetchDuplicates(t *testing.T) { eds := edstest.RandEDS(t, 4) root, err := share.NewRoot(eds) require.NoError(t, err) - fetcher := fetcher(ctx, t, newTestBlockstore(eds)) + fetcher := fetcher(ctx, t, eds) var wg sync.WaitGroup for i := range 100 { @@ -75,7 +72,13 @@ func TestFetchDuplicates(t *testing.T) { require.Zero(t, entries) } -func fetcher(ctx context.Context, t *testing.T, bstore blockstore.Blockstore) exchange.Fetcher { +func fetcher(ctx context.Context, t *testing.T, rsmt2dEds *rsmt2d.ExtendedDataSquare) exchange.Fetcher { + bstore := &Blockstore{ + Accessors: testAccessors{ + Accessor: eds.Rsmt2D{ExtendedDataSquare: rsmt2dEds}, + }, + } + net, err := mocknet.FullMeshLinked(2) require.NoError(t, err) @@ -103,60 +106,10 @@ func fetcher(ctx context.Context, t *testing.T, bstore blockstore.Blockstore) ex return bitswapClient } -type testBlockstore struct { - eds eds.Accessor -} - -func newTestBlockstore(rsmt2sEds *rsmt2d.ExtendedDataSquare) *testBlockstore { - return &testBlockstore{eds: eds.Rsmt2D{ExtendedDataSquare: rsmt2sEds}} -} - -func (b *testBlockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { - spec, ok := specRegistry[cid.Prefix().MhType] - if !ok { - return nil, fmt.Errorf("unsupported codec") - } - - bldr, err := spec.builder(cid) - if err != nil { - return nil, err - } - - return bldr.BlockFromEDS(ctx, b.eds) -} - -func (b *testBlockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { - blk, err := b.Get(ctx, cid) - if err != nil { - return 0, err - } - return len(blk.RawData()), nil -} - -func (b *testBlockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { - _, err := b.Get(ctx, cid) - if err != nil { - return false, err - } - return true, nil -} - -func (b *testBlockstore) Put(context.Context, blocks.Block) error { - panic("not implemented") -} - -func (b *testBlockstore) PutMany(context.Context, []blocks.Block) error { - panic("not implemented") -} - -func (b *testBlockstore) DeleteBlock(context.Context, cid.Cid) error { - panic("not implemented") -} - -func (b *testBlockstore) AllKeysChan(context.Context) (<-chan cid.Cid, error) { - panic("not implemented") +type testAccessors struct { + eds.Accessor } -func (b *testBlockstore) HashOnRead(bool) { - panic("not implemented") +func (t testAccessors) Get(context.Context, uint64) (eds.Accessor, error) { + return t.Accessor, nil } diff --git a/share/shwap/p2p/bitswap/block_store.go b/share/shwap/p2p/bitswap/block_store.go new file mode 100644 index 0000000000..1b4efbfa29 --- /dev/null +++ b/share/shwap/p2p/bitswap/block_store.go @@ -0,0 +1,70 @@ +package bitswap + +import ( + "context" + "fmt" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + + eds "github.com/celestiaorg/celestia-node/share/new_eds" +) + +type Accessors interface { + Get(ctx context.Context, height uint64) (eds.Accessor, error) +} + +type Blockstore struct { + Accessors +} + +func (b *Blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { + spec, ok := specRegistry[cid.Prefix().MhType] + if !ok { + return nil, fmt.Errorf("unsupported codec") + } + + blk, err := spec.builder(cid) + if err != nil { + return nil, err + } + + eds, err := b.Accessors.Get(ctx, blk.Height()) + if err != nil { + return nil, err + } + + return blk.BlockFromEDS(ctx, eds) +} + +func (b *Blockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { + blk, err := b.Get(ctx, cid) + if err != nil { + return 0, err + } + return len(blk.RawData()), nil +} + +func (b *Blockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { + _, err := b.Get(ctx, cid) + if err != nil { + return false, err + } + return true, nil +} + +func (b *Blockstore) Put(context.Context, blocks.Block) error { + panic("not implemented") +} + +func (b *Blockstore) PutMany(context.Context, []blocks.Block) error { + panic("not implemented") +} + +func (b *Blockstore) DeleteBlock(context.Context, cid.Cid) error { + panic("not implemented") +} + +func (b *Blockstore) AllKeysChan(context.Context) (<-chan cid.Cid, error) { panic("not implemented") } + +func (b *Blockstore) HashOnRead(bool) { panic("not implemented") } diff --git a/share/shwap/p2p/bitswap/row_block.go b/share/shwap/p2p/bitswap/row_block.go index f334229634..e540ceb85c 100644 --- a/share/shwap/p2p/bitswap/row_block.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -71,6 +71,10 @@ func (rb *RowBlock) CID() cid.Cid { return encodeCID(rb.ID, rowMultihashCode, rowCodec) } +func (rb *RowBlock) Height() uint64 { + return rb.ID.Height +} + func (rb *RowBlock) BlockFromEDS(ctx context.Context, eds eds.Accessor) (blocks.Block, error) { half, err := eds.AxisHalf(ctx, rsmt2d.Row, rb.ID.RowIndex) if err != nil { diff --git a/share/shwap/p2p/bitswap/row_block_test.go b/share/shwap/p2p/bitswap/row_block_test.go index da30cbaeac..5fefe59717 100644 --- a/share/shwap/p2p/bitswap/row_block_test.go +++ b/share/shwap/p2p/bitswap/row_block_test.go @@ -18,7 +18,7 @@ func TestRowRoundtrip_GetContainers(t *testing.T) { eds := edstest.RandEDS(t, 4) root, err := share.NewRoot(eds) require.NoError(t, err) - client := fetcher(ctx, t, newTestBlockstore(eds)) + client := fetcher(ctx, t, eds) blks := make([]Block, eds.Width()) for i := range blks { diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go index fa71bae863..1e45e4c01f 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -76,6 +76,10 @@ func (rndb *RowNamespaceDataBlock) CID() cid.Cid { return encodeCID(rndb.ID, rowNamespaceDataMultihashCode, rowNamespaceDataCodec) } +func (rndb *RowNamespaceDataBlock) Height() uint64 { + return rndb.ID.Height +} + func (rndb *RowNamespaceDataBlock) BlockFromEDS(ctx context.Context, eds eds.Accessor) (blocks.Block, error) { rnd, err := eds.RowNamespaceData(ctx, rndb.ID.DataNamespace, rndb.ID.RowIndex) if err != nil { diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block_test.go b/share/shwap/p2p/bitswap/row_namespace_data_block_test.go index df5971db09..cdbc402d73 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block_test.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block_test.go @@ -18,7 +18,7 @@ func TestRowNamespaceDataRoundtrip_GetContainers(t *testing.T) { namespace := sharetest.RandV0Namespace() eds, root := edstest.RandEDSWithNamespace(t, namespace, 64, 16) - client := fetcher(ctx, t, newTestBlockstore(eds)) + client := fetcher(ctx, t, eds) rowIdxs := share.RowsWithNamespace(root, namespace) blks := make([]Block, len(rowIdxs)) diff --git a/share/shwap/p2p/bitswap/sample_block.go b/share/shwap/p2p/bitswap/sample_block.go index 9192441e54..8acf8e1cc0 100644 --- a/share/shwap/p2p/bitswap/sample_block.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -71,6 +71,10 @@ func (sb *SampleBlock) CID() cid.Cid { return encodeCID(sb.ID, sampleMultihashCode, sampleCodec) } +func (sb *SampleBlock) Height() uint64 { + return sb.ID.Height +} + func (sb *SampleBlock) BlockFromEDS(ctx context.Context, eds eds.Accessor) (blocks.Block, error) { smpl, err := eds.Sample(ctx, sb.ID.RowIndex, sb.ID.ShareIndex) if err != nil { diff --git a/share/shwap/p2p/bitswap/sample_block_test.go b/share/shwap/p2p/bitswap/sample_block_test.go index 55156bbee1..7078cc6646 100644 --- a/share/shwap/p2p/bitswap/sample_block_test.go +++ b/share/shwap/p2p/bitswap/sample_block_test.go @@ -18,7 +18,7 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { eds := edstest.RandEDS(t, 8) root, err := share.NewRoot(eds) require.NoError(t, err) - client := fetcher(ctx, t, newTestBlockstore(eds)) + client := fetcher(ctx, t, eds) width := int(eds.Width()) blks := make([]Block, 0, width*width) From 11ee5c727bae4eb262623172a7e4f460501bfea1 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Fri, 14 Jun 2024 18:09:52 +0200 Subject: [PATCH 19/40] split BlockFromEDS into several methods --- share/shwap/p2p/bitswap/block.go | 40 +++---------------- share/shwap/p2p/bitswap/block_store.go | 32 ++++++++++++++- share/shwap/p2p/bitswap/row_block.go | 24 +++++++---- .../p2p/bitswap/row_namespace_data_block.go | 23 +++++++---- share/shwap/p2p/bitswap/sample_block.go | 23 +++++++---- 5 files changed, 86 insertions(+), 56 deletions(-) diff --git a/share/shwap/p2p/bitswap/block.go b/share/shwap/p2p/bitswap/block.go index 079097023b..3de529eef2 100644 --- a/share/shwap/p2p/bitswap/block.go +++ b/share/shwap/p2p/bitswap/block.go @@ -2,17 +2,12 @@ package bitswap import ( "context" - "fmt" - "github.com/gogo/protobuf/proto" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" logger "github.com/ipfs/go-log/v2" eds "github.com/celestiaorg/celestia-node/share/new_eds" - bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" - "github.com/celestiaorg/celestia-node/share" ) @@ -31,10 +26,12 @@ type Block interface { CID() cid.Cid // Height reports the Height the Shwap Container data behind the Block is from. Height() uint64 - // BlockFromEDS extract Bitswap Block out of the EDS. - // TODO: Split into MarshalBinary and Populate - BlockFromEDS(context.Context, eds.Accessor) (blocks.Block, error) - + // Marshal serializes bytes of Shwap Container the Block holds. + // Must not include the Shwap ID. + Marshal() ([]byte, error) + // Populate fills up the Block with the Shwap container getting it out of the EDS + // Accessor. + Populate(context.Context, eds.Accessor) error // IsEmpty reports whether the Block been populated with Shwap container. // If the Block is empty, it can be populated with Fetch. IsEmpty() bool @@ -42,28 +39,3 @@ type Block interface { // Population involves data validation against the Root. PopulateFn(*share.Root) PopulateFn } - -// toBlock converts given protobuf container into Bitswap Block. -func toBlock(cid cid.Cid, container proto.Marshaler) (blocks.Block, error) { - containerData, err := container.Marshal() - if err != nil { - return nil, fmt.Errorf("marshaling container: %w", err) - } - - blkProto := bitswappb.Block{ - Cid: cid.Bytes(), - Container: containerData, - } - - blkData, err := blkProto.Marshal() - if err != nil { - return nil, fmt.Errorf("marshaling Block protobuf: %w", err) - } - - blk, err := blocks.NewBlockWithCid(blkData, cid) - if err != nil { - return nil, fmt.Errorf("assembling Bitswap block: %w", err) - } - - return blk, nil -} diff --git a/share/shwap/p2p/bitswap/block_store.go b/share/shwap/p2p/bitswap/block_store.go index 1b4efbfa29..3e9a1b62b1 100644 --- a/share/shwap/p2p/bitswap/block_store.go +++ b/share/shwap/p2p/bitswap/block_store.go @@ -7,6 +7,8 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" + eds "github.com/celestiaorg/celestia-node/share/new_eds" ) @@ -34,10 +36,38 @@ func (b *Blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) return nil, err } - return blk.BlockFromEDS(ctx, eds) + if err = blk.Populate(ctx, eds); err != nil { + return nil, fmt.Errorf("failed to populate Shwap Block: %w", err) + } + + containerData, err := blk.Marshal() + if err != nil { + return nil, fmt.Errorf("marshaling Shwap container: %w", err) + } + + blkProto := bitswappb.Block{ + Cid: cid.Bytes(), + Container: containerData, + } + + blkData, err := blkProto.Marshal() + if err != nil { + return nil, fmt.Errorf("marshaling Bitswap Block protobuf: %w", err) + } + + bitswapBlk, err := blocks.NewBlockWithCid(blkData, cid) + if err != nil { + return nil, fmt.Errorf("assembling Bitswap block: %w", err) + } + + return bitswapBlk, nil } func (b *Blockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { + // TODO(@Wondertan): There must be a way to derive size without reading, proving, serializing and + // allocating Sample's block.Block or we could do hashing + // NOTE:Bitswap uses GetSize also to determine if we have content stored or not + // so simply returning constant size is not an option blk, err := b.Get(ctx, cid) if err != nil { return 0, err diff --git a/share/shwap/p2p/bitswap/row_block.go b/share/shwap/p2p/bitswap/row_block.go index e540ceb85c..585f515eec 100644 --- a/share/shwap/p2p/bitswap/row_block.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -5,7 +5,6 @@ import ( "fmt" "sync/atomic" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" eds "github.com/celestiaorg/celestia-node/share/new_eds" @@ -75,18 +74,29 @@ func (rb *RowBlock) Height() uint64 { return rb.ID.Height } -func (rb *RowBlock) BlockFromEDS(ctx context.Context, eds eds.Accessor) (blocks.Block, error) { - half, err := eds.AxisHalf(ctx, rsmt2d.Row, rb.ID.RowIndex) +func (rb *RowBlock) Marshal() ([]byte, error) { + if rb.IsEmpty() { + return nil, fmt.Errorf("cannot marshal empty RowBlock") + } + + container := rb.Container().ToProto() + containerData, err := container.Marshal() if err != nil { - return nil, fmt.Errorf("getting Row AxisHalf: %w", err) + return nil, fmt.Errorf("marshaling RowBlock container: %w", err) } - blk, err := toBlock(rb.CID(), half.ToRow().ToProto()) + return containerData, nil +} + +func (rb *RowBlock) Populate(ctx context.Context, eds eds.Accessor) error { + half, err := eds.AxisHalf(ctx, rsmt2d.Row, rb.ID.RowIndex) if err != nil { - return nil, fmt.Errorf("converting Row to Bitswap block: %w", err) + return fmt.Errorf("accessing Row AxisHalf: %w", err) } - return blk, nil + row := half.ToRow() + rb.container.Store(&row) + return nil } func (rb *RowBlock) IsEmpty() bool { diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go index 1e45e4c01f..aa8a85b349 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -5,7 +5,6 @@ import ( "fmt" "sync/atomic" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" eds "github.com/celestiaorg/celestia-node/share/new_eds" @@ -80,18 +79,28 @@ func (rndb *RowNamespaceDataBlock) Height() uint64 { return rndb.ID.Height } -func (rndb *RowNamespaceDataBlock) BlockFromEDS(ctx context.Context, eds eds.Accessor) (blocks.Block, error) { - rnd, err := eds.RowNamespaceData(ctx, rndb.ID.DataNamespace, rndb.ID.RowIndex) +func (rndb *RowNamespaceDataBlock) Marshal() ([]byte, error) { + if rndb.IsEmpty() { + return nil, fmt.Errorf("cannot marshal empty RowNamespaceDataBlock") + } + + container := rndb.Container().ToProto() + containerData, err := container.Marshal() if err != nil { - return nil, fmt.Errorf("getting RowNamespaceData: %w", err) + return nil, fmt.Errorf("marshaling RowNamespaceDataBlock container: %w", err) } - blk, err := toBlock(rndb.CID(), rnd.ToProto()) + return containerData, nil +} + +func (rndb *RowNamespaceDataBlock) Populate(ctx context.Context, eds eds.Accessor) error { + rnd, err := eds.RowNamespaceData(ctx, rndb.ID.DataNamespace, rndb.ID.RowIndex) if err != nil { - return nil, fmt.Errorf("converting RowNamespaceData to Bitswap block: %w", err) + return fmt.Errorf("accessing RowNamespaceData: %w", err) } - return blk, nil + rndb.container.Store(&rnd) + return nil } func (rndb *RowNamespaceDataBlock) IsEmpty() bool { diff --git a/share/shwap/p2p/bitswap/sample_block.go b/share/shwap/p2p/bitswap/sample_block.go index 8acf8e1cc0..7424c10d6e 100644 --- a/share/shwap/p2p/bitswap/sample_block.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -5,7 +5,6 @@ import ( "fmt" "sync/atomic" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" eds "github.com/celestiaorg/celestia-node/share/new_eds" @@ -75,18 +74,28 @@ func (sb *SampleBlock) Height() uint64 { return sb.ID.Height } -func (sb *SampleBlock) BlockFromEDS(ctx context.Context, eds eds.Accessor) (blocks.Block, error) { - smpl, err := eds.Sample(ctx, sb.ID.RowIndex, sb.ID.ShareIndex) +func (sb *SampleBlock) Marshal() ([]byte, error) { + if sb.IsEmpty() { + return nil, fmt.Errorf("cannot marshal empty SampleBlock") + } + + container := sb.Container().ToProto() + containerData, err := container.Marshal() if err != nil { - return nil, fmt.Errorf("getting Sample: %w", err) + return nil, fmt.Errorf("marshaling SampleBlock container: %w", err) } - blk, err := toBlock(sb.CID(), smpl.ToProto()) + return containerData, nil +} + +func (sb *SampleBlock) Populate(ctx context.Context, eds eds.Accessor) error { + smpl, err := eds.Sample(ctx, sb.ID.RowIndex, sb.ID.ShareIndex) if err != nil { - return nil, fmt.Errorf("converting Sample to Bitswap block: %w", err) + return fmt.Errorf("accessing Sample: %w", err) } - return blk, nil + sb.container.Store(&smpl) + return nil } func (sb *SampleBlock) IsEmpty() bool { From b25d59069211c09e6cd296fce1ec61b92ee03fb4 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Fri, 14 Jun 2024 19:29:55 +0200 Subject: [PATCH 20/40] populate -> marshal confusion --- share/shwap/p2p/bitswap/block.go | 29 +++++++------ share/shwap/p2p/bitswap/block_fetch.go | 42 +++++++++---------- share/shwap/p2p/bitswap/block_fetch_test.go | 4 +- share/shwap/p2p/bitswap/block_store.go | 2 +- share/shwap/p2p/bitswap/row_block.go | 2 +- .../p2p/bitswap/row_namespace_data_block.go | 2 +- share/shwap/p2p/bitswap/sample_block.go | 2 +- 7 files changed, 41 insertions(+), 42 deletions(-) diff --git a/share/shwap/p2p/bitswap/block.go b/share/shwap/p2p/bitswap/block.go index 3de529eef2..8b3160db7d 100644 --- a/share/shwap/p2p/bitswap/block.go +++ b/share/shwap/p2p/bitswap/block.go @@ -6,17 +6,12 @@ import ( "github.com/ipfs/go-cid" logger "github.com/ipfs/go-log/v2" - eds "github.com/celestiaorg/celestia-node/share/new_eds" - "github.com/celestiaorg/celestia-node/share" + eds "github.com/celestiaorg/celestia-node/share/new_eds" ) var log = logger.Logger("shwap/bitswap") -// PopulateFn is a closure produced by a Block that validates given -// serialized Shwap container and populates the Block with it on success. -type PopulateFn func([]byte) error - // Block represents Bitswap compatible Shwap container. // All Shwap containers must have a registerBlock-ed wrapper // implementing the interface to be compatible with Bitswap. @@ -26,16 +21,20 @@ type Block interface { CID() cid.Cid // Height reports the Height the Shwap Container data behind the Block is from. Height() uint64 - // Marshal serializes bytes of Shwap Container the Block holds. - // Must not include the Shwap ID. - Marshal() ([]byte, error) + + // IsEmpty reports whether the Block holds respective Shwap container. + IsEmpty() bool // Populate fills up the Block with the Shwap container getting it out of the EDS // Accessor. Populate(context.Context, eds.Accessor) error - // IsEmpty reports whether the Block been populated with Shwap container. - // If the Block is empty, it can be populated with Fetch. - IsEmpty() bool - // PopulateFn returns closure that fills up the Block with Shwap container. - // Population involves data validation against the Root. - PopulateFn(*share.Root) PopulateFn + // Marshal serializes bytes of Shwap Container the Block holds. + // Must not include the Shwap ID. + Marshal() ([]byte, error) + // UnmarshalFn returns closure that unmarshal the Block with Shwap container. + // Unmarshalling involves data validation against the Root. + UnmarshalFn(*share.Root) UnmarshalFn } + +// UnmarshalFn is a closure produced by a Block that unmarshalls and validates +// given serialized Shwap container and populates the Block with it on success. +type UnmarshalFn func([]byte) error diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index bed918451e..f2b2594f0b 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -30,10 +30,10 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks cid := blk.CID() fetching[cid] = blk // mark block as fetching cids = append(cids, cid) - // store the PopulateFn s.t. hasher can access it + // store the UnmarshalFn s.t. hasher can access it // and fill in the Block - populate := blk.PopulateFn(root) - populatorFns.LoadOrStore(cid, populate) + populate := blk.UnmarshalFn(root) + unmarshalFns.LoadOrStore(cid, populate) } blkCh, err := fetcher.GetBlocks(ctx, cids) @@ -48,14 +48,14 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks continue } // uncommon duplicate case: concurrent fetching of the same block, - // so we have to populate it ourselves instead of the hasher, - populateFn := blk.PopulateFn(root) - _, err := populate(populateFn, bitswapBlk.RawData()) + // so we have to unmarshal it ourselves instead of the hasher, + unmarshalFn := blk.UnmarshalFn(root) + _, err := unmarshal(unmarshalFn, bitswapBlk.RawData()) if err != nil { // this means verification succeeded in the hasher but failed here // this case should never happen in practice // and if so something is really wrong - panic(fmt.Sprintf("populating duplicate block: %s", err)) + panic(fmt.Sprintf("unmarshaling duplicate block: %s", err)) } // NOTE: This approach has a downside that we redo deserialization and computationally // expensive computation for as many duplicates. We tried solutions that doesn't have this @@ -66,9 +66,9 @@ func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks return ctx.Err() } -// populate populates the data into a Block via PopulateFn -// If populate is nil -- gets it from the global populatorFns. -func populate(populate PopulateFn, data []byte) ([]byte, error) { +// unmarshal unmarshalls the Shwap Container data into a Block via UnmarshalFn +// If unmarshalFn is nil -- gets it from the global unmarshalFns. +func unmarshal(unmarshalFn UnmarshalFn, data []byte) ([]byte, error) { var blk bitswappb.Block err := blk.Unmarshal(data) if err != nil { @@ -84,17 +84,17 @@ func populate(populate PopulateFn, data []byte) ([]byte, error) { return nil, fmt.Errorf("validating cid: %w", err) } - if populate == nil { - // get registered PopulateFn and use it to check data validity and + if unmarshalFn == nil { + // get registered UnmarshalFn and use it to check data validity and // pass it to Fetch caller - val, ok := populatorFns.LoadAndDelete(cid) + val, ok := unmarshalFns.LoadAndDelete(cid) if !ok { return nil, fmt.Errorf("no populator registered for %s", cid.String()) } - populate = val.(PopulateFn) + unmarshalFn = val.(UnmarshalFn) } - err = populate(blk.Container) + err = unmarshalFn(blk.Container) if err != nil { return nil, fmt.Errorf("verifying data: %w", err) } @@ -102,18 +102,18 @@ func populate(populate PopulateFn, data []byte) ([]byte, error) { return id, nil } -// populatorFns exist to communicate between Fetch and hasher. +// unmarshalFns exist to communicate between Fetch and hasher. // -// Fetch registers PopulateFNs that hasher then uses to validate and populate Block responses coming +// Fetch registers UnmarshalFNs that hasher then uses to validate and unmarshal Block responses coming // through Bitswap // // Bitswap does not provide *stateful* verification out of the box and by default // messages are verified by their respective MultiHashes that are registered globally. -// For every Block type there is a global hasher registered that accesses stored PopulateFn once a -// message is received. It then uses PopulateFn to validate and fill in the respective Block +// For every Block type there is a global hasher registered that accesses stored UnmarshalFn once a +// message is received. It then uses UnmarshalFn to validate and fill in the respective Block // // sync.Map is used to minimize contention for disjoint keys -var populatorFns sync.Map +var unmarshalFns sync.Map // hasher implements hash.Hash to be registered as custom multihash // hasher is the *hack* to inject custom verification logic into Bitswap @@ -125,7 +125,7 @@ type hasher struct { } func (h *hasher) Write(data []byte) (int, error) { - id, err := populate(nil, data) + id, err := unmarshal(nil, data) if err != nil { err = fmt.Errorf("hasher: %w", err) log.Error(err) diff --git a/share/shwap/p2p/bitswap/block_fetch_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go index 85d4bad0c7..f23f2ff495 100644 --- a/share/shwap/p2p/bitswap/block_fetch_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -64,8 +64,8 @@ func TestFetchDuplicates(t *testing.T) { wg.Wait() var entries int - populatorFns.Range(func(key, _ any) bool { - populatorFns.Delete(key) + unmarshalFns.Range(func(key, _ any) bool { + unmarshalFns.Delete(key) entries++ return true }) diff --git a/share/shwap/p2p/bitswap/block_store.go b/share/shwap/p2p/bitswap/block_store.go index 3e9a1b62b1..d3a818c671 100644 --- a/share/shwap/p2p/bitswap/block_store.go +++ b/share/shwap/p2p/bitswap/block_store.go @@ -37,7 +37,7 @@ func (b *Blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) } if err = blk.Populate(ctx, eds); err != nil { - return nil, fmt.Errorf("failed to populate Shwap Block: %w", err) + return nil, fmt.Errorf("failed to unmarshal Shwap Block: %w", err) } containerData, err := blk.Marshal() diff --git a/share/shwap/p2p/bitswap/row_block.go b/share/shwap/p2p/bitswap/row_block.go index 585f515eec..06fdfc4a53 100644 --- a/share/shwap/p2p/bitswap/row_block.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -107,7 +107,7 @@ func (rb *RowBlock) Container() *shwap.Row { return rb.container.Load() } -func (rb *RowBlock) PopulateFn(root *share.Root) PopulateFn { +func (rb *RowBlock) UnmarshalFn(root *share.Root) UnmarshalFn { return func(data []byte) error { if !rb.IsEmpty() { return nil diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go index aa8a85b349..f2a32bea37 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -111,7 +111,7 @@ func (rndb *RowNamespaceDataBlock) Container() *shwap.RowNamespaceData { return rndb.container.Load() } -func (rndb *RowNamespaceDataBlock) PopulateFn(root *share.Root) PopulateFn { +func (rndb *RowNamespaceDataBlock) UnmarshalFn(root *share.Root) UnmarshalFn { return func(data []byte) error { if !rndb.IsEmpty() { return nil diff --git a/share/shwap/p2p/bitswap/sample_block.go b/share/shwap/p2p/bitswap/sample_block.go index 7424c10d6e..2831ef0893 100644 --- a/share/shwap/p2p/bitswap/sample_block.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -106,7 +106,7 @@ func (sb *SampleBlock) Container() *shwap.Sample { return sb.container.Load() } -func (sb *SampleBlock) PopulateFn(root *share.Root) PopulateFn { +func (sb *SampleBlock) UnmarshalFn(root *share.Root) UnmarshalFn { return func(data []byte) error { if !sb.IsEmpty() { return nil From 9bd193fbf975d9b6659a212fe57de7957eaf6b11 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Sat, 15 Jun 2024 03:56:53 +0200 Subject: [PATCH 21/40] improve bitswap tests and ensure there are multiple servers in the setup --- share/shwap/p2p/bitswap/block_fetch_test.go | 57 +++++++++++++-------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go index f23f2ff495..229ef04f05 100644 --- a/share/shwap/p2p/bitswap/block_fetch_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -3,19 +3,20 @@ package bitswap import ( "context" "math/rand/v2" - "runtime" "sync" "testing" "time" - "github.com/ipfs/boxo/bitswap" + "github.com/ipfs/boxo/bitswap/client" "github.com/ipfs/boxo/bitswap/network" + "github.com/ipfs/boxo/bitswap/server" "github.com/ipfs/boxo/blockstore" "github.com/ipfs/boxo/exchange" "github.com/ipfs/boxo/routing/offline" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" record "github.com/libp2p/go-libp2p-record" + "github.com/libp2p/go-libp2p/core/host" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -29,7 +30,6 @@ import ( ) func TestFetchDuplicates(t *testing.T) { - runtime.GOMAXPROCS(3) ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) defer cancel() @@ -50,7 +50,7 @@ func TestFetchDuplicates(t *testing.T) { wg.Add(1) go func(i int) { rint := rand.IntN(10) - // this sleep ensures fetches aren't started simultaneously allowing to check for edge-cases + // this sleep ensures fetches aren't started simultaneously, allowing to check for edge-cases time.Sleep(time.Millisecond * time.Duration(rint)) err := Fetch(ctx, fetcher, root, blks...) @@ -72,38 +72,53 @@ func TestFetchDuplicates(t *testing.T) { require.Zero(t, entries) } -func fetcher(ctx context.Context, t *testing.T, rsmt2dEds *rsmt2d.ExtendedDataSquare) exchange.Fetcher { +func fetcher(ctx context.Context, t *testing.T, rsmt2dEds *rsmt2d.ExtendedDataSquare) exchange.SessionExchange { bstore := &Blockstore{ Accessors: testAccessors{ Accessor: eds.Rsmt2D{ExtendedDataSquare: rsmt2dEds}, }, } - net, err := mocknet.FullMeshLinked(2) + net, err := mocknet.FullMeshLinked(3) require.NoError(t, err) + newServer(ctx, net.Hosts()[0], bstore) + newServer(ctx, net.Hosts()[1], bstore) + + client := newClient(ctx, net.Hosts()[2], bstore) + + err = net.ConnectAllButSelf() + require.NoError(t, err) + return client +} + +func newServer(ctx context.Context, host host.Host, store blockstore.Blockstore) { dstore := dssync.MutexWrap(ds.NewMapDatastore()) routing := offline.NewOfflineRouter(dstore, record.NamespacedValidator{}) - _ = bitswap.New( + net := network.NewFromIpfsHost(host, routing) + server := server.New( ctx, - network.NewFromIpfsHost(net.Hosts()[0], routing), - bstore, + net, + store, + server.TaskWorkerCount(2), + server.EngineTaskWorkerCount(2), + server.ProvideEnabled(false), + server.SetSendDontHaves(false), ) + net.Start(server) +} - dstoreClient := dssync.MutexWrap(ds.NewMapDatastore()) - bstoreClient := blockstore.NewBlockstore(dstoreClient) - routingClient := offline.NewOfflineRouter(dstoreClient, record.NamespacedValidator{}) - - bitswapClient := bitswap.New( +func newClient(ctx context.Context, host host.Host, store blockstore.Blockstore) *client.Client { + dstore := dssync.MutexWrap(ds.NewMapDatastore()) + routing := offline.NewOfflineRouter(dstore, record.NamespacedValidator{}) + net := network.NewFromIpfsHost(host, routing) + client := client.New( ctx, - network.NewFromIpfsHost(net.Hosts()[1], routingClient), - bstoreClient, + net, + store, ) - - err = net.ConnectAllButSelf() - require.NoError(t, err) - - return bitswapClient + net.Start(client) + return client } type testAccessors struct { From 963b3dad404a3d8df35c91f007ea9aae061ff82b Mon Sep 17 00:00:00 2001 From: Wondertan Date: Sat, 15 Jun 2024 04:29:59 +0200 Subject: [PATCH 22/40] notifyblock, cleanup on defer and revert duplicates and atomics --- share/shwap/p2p/bitswap/block_fetch.go | 41 ++++++++++++------- share/shwap/p2p/bitswap/row_block.go | 17 +++----- share/shwap/p2p/bitswap/row_block_test.go | 2 +- .../p2p/bitswap/row_namespace_data_block.go | 16 +++----- .../bitswap/row_namespace_data_block_test.go | 2 +- share/shwap/p2p/bitswap/sample_block.go | 16 +++----- share/shwap/p2p/bitswap/sample_block_test.go | 2 +- share/shwap/row.go | 5 +++ share/shwap/row_namespace_data.go | 5 +++ share/shwap/sample.go | 5 +++ 10 files changed, 63 insertions(+), 48 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index f2b2594f0b..ea05a50611 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -19,31 +19,44 @@ import ( // Validates Block against the given Root and skips Blocks that are already populated. // Gracefully synchronize identical Blocks requested simultaneously. // Blocks until either context is canceled or all Blocks are fetched and populated. -func Fetch(ctx context.Context, fetcher exchange.Fetcher, root *share.Root, blks ...Block) error { +func Fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks ...Block) error { cids := make([]cid.Cid, 0, len(blks)) - fetching := make(map[cid.Cid]Block) + duplicates := make(map[cid.Cid]Block) for _, blk := range blks { if !blk.IsEmpty() { continue // skip populated Blocks } - // memoize CID for reuse as it ain't free - cid := blk.CID() - fetching[cid] = blk // mark block as fetching + + cid := blk.CID() // memoize CID for reuse as it ain't free cids = append(cids, cid) + // store the UnmarshalFn s.t. hasher can access it // and fill in the Block - populate := blk.UnmarshalFn(root) - unmarshalFns.LoadOrStore(cid, populate) + unmarshalFn := blk.UnmarshalFn(root) + _, exists := unmarshalFns.LoadOrStore(cid, unmarshalFn) + if exists { + // the unmarshalFn has already been stored for the cid + // means there is ongoing fetch happening for the same cid + duplicates[cid] = blk // so mark the Block as duplicate + } else { + // cleanup are by the original requester and + // only after we are sure we got the block + defer unmarshalFns.Delete(cid) + } } - blkCh, err := fetcher.GetBlocks(ctx, cids) + blkCh, err := exchg.GetBlocks(ctx, cids) if err != nil { - return fmt.Errorf("fetching bitswap blocks: %w", err) + return fmt.Errorf("requesting Bitswap blocks: %w", err) } for bitswapBlk := range blkCh { // GetBlocks closes blkCh on ctx cancellation - blk := fetching[bitswapBlk.Cid()] - if !blk.IsEmpty() { + if err := exchg.NotifyNewBlocks(ctx, bitswapBlk); err != nil { + log.Error("failed to notify new Bitswap blocks: %w", err) + } + + blk, ok := duplicates[bitswapBlk.Cid()] + if !ok { // common case: the block was populated by the hasher, so skip continue } @@ -87,9 +100,9 @@ func unmarshal(unmarshalFn UnmarshalFn, data []byte) ([]byte, error) { if unmarshalFn == nil { // get registered UnmarshalFn and use it to check data validity and // pass it to Fetch caller - val, ok := unmarshalFns.LoadAndDelete(cid) + val, ok := unmarshalFns.Load(cid) if !ok { - return nil, fmt.Errorf("no populator registered for %s", cid.String()) + return nil, fmt.Errorf("no unmarshallers registered for %s", cid.String()) } unmarshalFn = val.(UnmarshalFn) } @@ -102,7 +115,7 @@ func unmarshal(unmarshalFn UnmarshalFn, data []byte) ([]byte, error) { return id, nil } -// unmarshalFns exist to communicate between Fetch and hasher. +// unmarshalFns exist to communicate between Fetch and hasher, and it's global as a necessity // // Fetch registers UnmarshalFNs that hasher then uses to validate and unmarshal Block responses coming // through Bitswap diff --git a/share/shwap/p2p/bitswap/row_block.go b/share/shwap/p2p/bitswap/row_block.go index 06fdfc4a53..78eec3f1d5 100644 --- a/share/shwap/p2p/bitswap/row_block.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -3,7 +3,6 @@ package bitswap import ( "context" "fmt" - "sync/atomic" "github.com/ipfs/go-cid" @@ -39,7 +38,7 @@ func init() { type RowBlock struct { ID shwap.RowID - container atomic.Pointer[shwap.Row] + Container shwap.Row } // NewEmptyRowBlock constructs a new empty RowBlock. @@ -79,7 +78,7 @@ func (rb *RowBlock) Marshal() ([]byte, error) { return nil, fmt.Errorf("cannot marshal empty RowBlock") } - container := rb.Container().ToProto() + container := rb.Container.ToProto() containerData, err := container.Marshal() if err != nil { return nil, fmt.Errorf("marshaling RowBlock container: %w", err) @@ -94,17 +93,12 @@ func (rb *RowBlock) Populate(ctx context.Context, eds eds.Accessor) error { return fmt.Errorf("accessing Row AxisHalf: %w", err) } - row := half.ToRow() - rb.container.Store(&row) + rb.Container = half.ToRow() return nil } func (rb *RowBlock) IsEmpty() bool { - return rb.Container() == nil -} - -func (rb *RowBlock) Container() *shwap.Row { - return rb.container.Load() + return rb.Container.IsEmpty() } func (rb *RowBlock) UnmarshalFn(root *share.Root) UnmarshalFn { @@ -121,7 +115,8 @@ func (rb *RowBlock) UnmarshalFn(root *share.Root) UnmarshalFn { if err := cntr.Validate(root, rb.ID.RowIndex); err != nil { return fmt.Errorf("validating Row: %w", err) } - rb.container.Store(&cntr) + + rb.Container = cntr return nil } } diff --git a/share/shwap/p2p/bitswap/row_block_test.go b/share/shwap/p2p/bitswap/row_block_test.go index 5fefe59717..27c6b12f16 100644 --- a/share/shwap/p2p/bitswap/row_block_test.go +++ b/share/shwap/p2p/bitswap/row_block_test.go @@ -32,7 +32,7 @@ func TestRowRoundtrip_GetContainers(t *testing.T) { for _, blk := range blks { row := blk.(*RowBlock) - err = row.Container().Validate(root, row.ID.RowIndex) + err = row.Container.Validate(root, row.ID.RowIndex) require.NoError(t, err) } } diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go index f2a32bea37..df86bf5dbd 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -3,7 +3,6 @@ package bitswap import ( "context" "fmt" - "sync/atomic" "github.com/ipfs/go-cid" @@ -38,7 +37,7 @@ func init() { type RowNamespaceDataBlock struct { ID shwap.RowNamespaceDataID - container atomic.Pointer[shwap.RowNamespaceData] + Container shwap.RowNamespaceData } // NewEmptyRowNamespaceDataBlock constructs a new empty RowNamespaceDataBlock. @@ -84,7 +83,7 @@ func (rndb *RowNamespaceDataBlock) Marshal() ([]byte, error) { return nil, fmt.Errorf("cannot marshal empty RowNamespaceDataBlock") } - container := rndb.Container().ToProto() + container := rndb.Container.ToProto() containerData, err := container.Marshal() if err != nil { return nil, fmt.Errorf("marshaling RowNamespaceDataBlock container: %w", err) @@ -99,16 +98,12 @@ func (rndb *RowNamespaceDataBlock) Populate(ctx context.Context, eds eds.Accesso return fmt.Errorf("accessing RowNamespaceData: %w", err) } - rndb.container.Store(&rnd) + rndb.Container = rnd return nil } func (rndb *RowNamespaceDataBlock) IsEmpty() bool { - return rndb.Container() == nil -} - -func (rndb *RowNamespaceDataBlock) Container() *shwap.RowNamespaceData { - return rndb.container.Load() + return rndb.Container.IsEmpty() } func (rndb *RowNamespaceDataBlock) UnmarshalFn(root *share.Root) UnmarshalFn { @@ -125,7 +120,8 @@ func (rndb *RowNamespaceDataBlock) UnmarshalFn(root *share.Root) UnmarshalFn { if err := cntr.Validate(root, rndb.ID.DataNamespace, rndb.ID.RowIndex); err != nil { return fmt.Errorf("validating RowNamespaceData: %w", err) } - rndb.container.Store(&cntr) + + rndb.Container = cntr return nil } } diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block_test.go b/share/shwap/p2p/bitswap/row_namespace_data_block_test.go index cdbc402d73..fd538fc925 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block_test.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block_test.go @@ -33,7 +33,7 @@ func TestRowNamespaceDataRoundtrip_GetContainers(t *testing.T) { for _, blk := range blks { rnd := blk.(*RowNamespaceDataBlock) - err = rnd.Container().Validate(root, rnd.ID.DataNamespace, rnd.ID.RowIndex) + err = rnd.Container.Validate(root, rnd.ID.DataNamespace, rnd.ID.RowIndex) require.NoError(t, err) } } diff --git a/share/shwap/p2p/bitswap/sample_block.go b/share/shwap/p2p/bitswap/sample_block.go index 2831ef0893..0984b06827 100644 --- a/share/shwap/p2p/bitswap/sample_block.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -3,7 +3,6 @@ package bitswap import ( "context" "fmt" - "sync/atomic" "github.com/ipfs/go-cid" @@ -38,7 +37,7 @@ func init() { // SampleBlock is a Bitswap compatible block for Shwap's Sample container. type SampleBlock struct { ID shwap.SampleID - container atomic.Pointer[shwap.Sample] + Container shwap.Sample } // NewEmptySampleBlock constructs a new empty SampleBlock. @@ -79,7 +78,7 @@ func (sb *SampleBlock) Marshal() ([]byte, error) { return nil, fmt.Errorf("cannot marshal empty SampleBlock") } - container := sb.Container().ToProto() + container := sb.Container.ToProto() containerData, err := container.Marshal() if err != nil { return nil, fmt.Errorf("marshaling SampleBlock container: %w", err) @@ -94,16 +93,12 @@ func (sb *SampleBlock) Populate(ctx context.Context, eds eds.Accessor) error { return fmt.Errorf("accessing Sample: %w", err) } - sb.container.Store(&smpl) + sb.Container = smpl return nil } func (sb *SampleBlock) IsEmpty() bool { - return sb.Container() == nil -} - -func (sb *SampleBlock) Container() *shwap.Sample { - return sb.container.Load() + return sb.Container.IsEmpty() } func (sb *SampleBlock) UnmarshalFn(root *share.Root) UnmarshalFn { @@ -120,7 +115,8 @@ func (sb *SampleBlock) UnmarshalFn(root *share.Root) UnmarshalFn { if err := cntr.Validate(root, sb.ID.RowIndex, sb.ID.ShareIndex); err != nil { return fmt.Errorf("validating Sample: %w", err) } - sb.container.Store(&cntr) + + sb.Container = cntr return nil } } diff --git a/share/shwap/p2p/bitswap/sample_block_test.go b/share/shwap/p2p/bitswap/sample_block_test.go index 7078cc6646..c74bec8ac5 100644 --- a/share/shwap/p2p/bitswap/sample_block_test.go +++ b/share/shwap/p2p/bitswap/sample_block_test.go @@ -35,7 +35,7 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { for _, sample := range blks { blk := sample.(*SampleBlock) - err = blk.Container().Validate(root, blk.ID.RowIndex, blk.ID.ShareIndex) + err = blk.Container.Validate(root, blk.ID.RowIndex, blk.ID.ShareIndex) require.NoError(t, err) } } diff --git a/share/shwap/row.go b/share/shwap/row.go index f03c7366f5..c1126cd8a7 100644 --- a/share/shwap/row.go +++ b/share/shwap/row.go @@ -75,6 +75,11 @@ func (r Row) ToProto() *pb.Row { } } +// IsEmpty reports whether the Row is empty, i.e. doesn't contain any shares. +func (r Row) IsEmpty() bool { + return r.halfShares == nil +} + // Validate checks if the row's shares match the expected number from the root data and validates // the side of the row. func (r Row) Validate(dah *share.Root, idx int) error { diff --git a/share/shwap/row_namespace_data.go b/share/shwap/row_namespace_data.go index 5d424ee0f3..07ffd8f967 100644 --- a/share/shwap/row_namespace_data.go +++ b/share/shwap/row_namespace_data.go @@ -133,6 +133,11 @@ func (rnd RowNamespaceData) ToProto() *pb.RowNamespaceData { } } +// IsEmpty reports whether the RowNamespaceData is empty, i.e. doesn't contain a proof. +func (rnd RowNamespaceData) IsEmpty() bool { + return rnd.Proof == nil +} + // Validate checks validity of the RowNamespaceData against the Root, Namespace and Row index. func (rnd RowNamespaceData) Validate(dah *share.Root, namespace share.Namespace, rowIdx int) error { if rnd.Proof == nil || rnd.Proof.IsEmptyProof() { diff --git a/share/shwap/sample.go b/share/shwap/sample.go index cb263415ad..25c6da609b 100644 --- a/share/shwap/sample.go +++ b/share/shwap/sample.go @@ -78,6 +78,11 @@ func (s Sample) ToProto() *pb.Sample { } } +// IsEmpty reports whether the Sample is empty, i.e. doesn't contain a proof. +func (s Sample) IsEmpty() bool { + return s.Proof == nil +} + // Validate checks the inclusion of the share using its Merkle proof under the specified root. // Returns an error if the proof is invalid or does not correspond to the indicated proof type. func (s Sample) Validate(dah *share.Root, rowIdx, colIdx int) error { From e84e349adc523e0066980f8eb9e1707947c458fd Mon Sep 17 00:00:00 2001 From: Wondertan Date: Sat, 15 Jun 2024 04:47:32 +0200 Subject: [PATCH 23/40] improve test for samples --- share/shwap/p2p/bitswap/sample_block_test.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/share/shwap/p2p/bitswap/sample_block_test.go b/share/shwap/p2p/bitswap/sample_block_test.go index c74bec8ac5..fdc5517eeb 100644 --- a/share/shwap/p2p/bitswap/sample_block_test.go +++ b/share/shwap/p2p/bitswap/sample_block_test.go @@ -15,7 +15,7 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - eds := edstest.RandEDS(t, 8) + eds := edstest.RandEDS(t, 16) root, err := share.NewRoot(eds) require.NoError(t, err) client := fetcher(ctx, t, eds) @@ -30,8 +30,13 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { } } - err = Fetch(ctx, client, root, blks...) - require.NoError(t, err) + // NOTE: this the limiting number of items bitswap can do in a single Fetch + // going beyond that cause Bitswap to stall indefinitely + const maxPerFetch = 1024 + for i := range len(blks) / maxPerFetch { + err = Fetch(ctx, client, root, blks[i*maxPerFetch:(i+1)*maxPerFetch]...) + require.NoError(t, err) + } for _, sample := range blks { blk := sample.(*SampleBlock) From 7732ce21eec011b19d1b953206e90ccd0cd9cdb2 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Sat, 15 Jun 2024 05:16:10 +0200 Subject: [PATCH 24/40] synchronize access to marshallers --- share/shwap/p2p/bitswap/block_fetch.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index ea05a50611..a51d210f41 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -33,7 +33,7 @@ func Fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks // store the UnmarshalFn s.t. hasher can access it // and fill in the Block unmarshalFn := blk.UnmarshalFn(root) - _, exists := unmarshalFns.LoadOrStore(cid, unmarshalFn) + _, exists := unmarshalFns.LoadOrStore(cid, &unmarshalEntry{UnmarshalFn: unmarshalFn}) if exists { // the unmarshalFn has already been stored for the cid // means there is ongoing fetch happening for the same cid @@ -104,7 +104,11 @@ func unmarshal(unmarshalFn UnmarshalFn, data []byte) ([]byte, error) { if !ok { return nil, fmt.Errorf("no unmarshallers registered for %s", cid.String()) } - unmarshalFn = val.(UnmarshalFn) + entry := val.(*unmarshalEntry) + + entry.Lock() + defer entry.Unlock() + unmarshalFn = entry.UnmarshalFn } err = unmarshalFn(blk.Container) @@ -128,6 +132,11 @@ func unmarshal(unmarshalFn UnmarshalFn, data []byte) ([]byte, error) { // sync.Map is used to minimize contention for disjoint keys var unmarshalFns sync.Map +type unmarshalEntry struct { + sync.Mutex + UnmarshalFn +} + // hasher implements hash.Hash to be registered as custom multihash // hasher is the *hack* to inject custom verification logic into Bitswap type hasher struct { From a6ec6508cb6f34edc6183bbc010f6859829f71d0 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Sat, 15 Jun 2024 05:28:51 +0200 Subject: [PATCH 25/40] remove redundant allowlist --- share/shwap/p2p/bitswap/cid.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/share/shwap/p2p/bitswap/cid.go b/share/shwap/p2p/bitswap/cid.go index 53129393da..ea65715d2e 100644 --- a/share/shwap/p2p/bitswap/cid.go +++ b/share/shwap/p2p/bitswap/cid.go @@ -8,20 +8,6 @@ import ( mh "github.com/multiformats/go-multihash" ) -// DefaultAllowlist keeps default list of multihashes allowed in the network. -// TODO(@Wondertan): Make it private and instead provide Blockservice constructor with injected -// -// allowlist -var DefaultAllowlist allowlist - -type allowlist struct{} - -func (a allowlist) IsAllowed(code uint64) bool { - // we disable all codes except registered - _, ok := specRegistry[code] - return ok -} - // extractCID retrieves Shwap ID out of the CID. func extractCID(cid cid.Cid) ([]byte, error) { if err := validateCID(cid); err != nil { From ef45389222de06fde033b875a314e4d85ebb9428 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Thu, 20 Jun 2024 19:40:40 +0200 Subject: [PATCH 26/40] sessions and fetch limits --- share/shwap/p2p/bitswap/block_fetch.go | 52 +++++++++++++++++-- share/shwap/p2p/bitswap/block_fetch_test.go | 3 +- share/shwap/p2p/bitswap/block_store.go | 3 +- share/shwap/p2p/bitswap/row_block.go | 3 +- .../p2p/bitswap/row_namespace_data_block.go | 6 +-- share/shwap/p2p/bitswap/sample_block.go | 6 +-- share/shwap/p2p/bitswap/sample_block_test.go | 14 ++--- 7 files changed, 61 insertions(+), 26 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index a51d210f41..d741f89c5a 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -9,17 +9,48 @@ import ( "github.com/ipfs/boxo/exchange" "github.com/ipfs/go-cid" - bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" - "github.com/celestiaorg/celestia-node/share" + bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" ) +// WithFetchSession instantiates a new Fetch session and saves it on the context. +// Session reuses peers known to have Blocks without rediscovering. +func WithFetchSession(ctx context.Context, exchg exchange.SessionExchange) context.Context { + fetcher := exchg.NewSession(ctx) + return context.WithValue(ctx, fetcherKey, fetcher) +} + // Fetch fetches and populates given Blocks using Fetcher wrapping Bitswap. // // Validates Block against the given Root and skips Blocks that are already populated. // Gracefully synchronize identical Blocks requested simultaneously. // Blocks until either context is canceled or all Blocks are fetched and populated. func Fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks ...Block) error { + for from, to := 0, 0; to < len(blks); { //nolint:wastedassign // it's not actually wasted + from, to = to, to+maxPerFetch + if to >= len(blks) { + to = len(blks) + } + + err := fetch(ctx, exchg, root, blks[from:to]...) + if err != nil { + return err + } + } + + return ctx.Err() +} + +// maxPerFetch sets the limit for maximum items in a single fetch. +// It's a heuristic coming from Bitswap, which apparently can't process more than ~1024 in a single +// GetBlock call. Going beyond that stalls the call indefinitely. +const maxPerFetch = 1024 + +// fetch fetches given Blocks. +// See [Fetch] for detailed description. +func fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks ...Block) error { + fetcher := getFetcher(ctx, exchg) + cids := make([]cid.Cid, 0, len(blks)) duplicates := make(map[cid.Cid]Block) for _, blk := range blks { @@ -45,7 +76,7 @@ func Fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks } } - blkCh, err := exchg.GetBlocks(ctx, cids) + blkCh, err := fetcher.GetBlocks(ctx, cids) if err != nil { return fmt.Errorf("requesting Bitswap blocks: %w", err) } @@ -175,3 +206,18 @@ func (h *hasher) Size() int { func (h *hasher) BlockSize() int { return sha256.BlockSize } + +type fetcherSessionKey struct{} + +var fetcherKey fetcherSessionKey + +// getFetcher takes context and a fetcher, if there is another fetcher in the context, +// it gets returned. +func getFetcher(ctx context.Context, fetcher exchange.Fetcher) exchange.Fetcher { + fetcherVal := ctx.Value(fetcherKey) + if fetcherVal == nil { + return fetcher + } + + return fetcherVal.(exchange.Fetcher) +} diff --git a/share/shwap/p2p/bitswap/block_fetch_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go index 229ef04f05..f3116ca7dc 100644 --- a/share/shwap/p2p/bitswap/block_fetch_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -21,12 +21,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - eds "github.com/celestiaorg/celestia-node/share/new_eds" - "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/edstest" + eds "github.com/celestiaorg/celestia-node/share/new_eds" ) func TestFetchDuplicates(t *testing.T) { diff --git a/share/shwap/p2p/bitswap/block_store.go b/share/shwap/p2p/bitswap/block_store.go index d3a818c671..72994301d5 100644 --- a/share/shwap/p2p/bitswap/block_store.go +++ b/share/shwap/p2p/bitswap/block_store.go @@ -7,9 +7,8 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" - eds "github.com/celestiaorg/celestia-node/share/new_eds" + bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" ) type Accessors interface { diff --git a/share/shwap/p2p/bitswap/row_block.go b/share/shwap/p2p/bitswap/row_block.go index 78eec3f1d5..97513fd10a 100644 --- a/share/shwap/p2p/bitswap/row_block.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -6,11 +6,10 @@ import ( "github.com/ipfs/go-cid" - eds "github.com/celestiaorg/celestia-node/share/new_eds" - "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" + eds "github.com/celestiaorg/celestia-node/share/new_eds" "github.com/celestiaorg/celestia-node/share/shwap" shwappb "github.com/celestiaorg/celestia-node/share/shwap/pb" ) diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go index df86bf5dbd..8c3f15ab64 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -6,12 +6,10 @@ import ( "github.com/ipfs/go-cid" - eds "github.com/celestiaorg/celestia-node/share/new_eds" - - shwappb "github.com/celestiaorg/celestia-node/share/shwap/pb" - "github.com/celestiaorg/celestia-node/share" + eds "github.com/celestiaorg/celestia-node/share/new_eds" "github.com/celestiaorg/celestia-node/share/shwap" + shwappb "github.com/celestiaorg/celestia-node/share/shwap/pb" ) const ( diff --git a/share/shwap/p2p/bitswap/sample_block.go b/share/shwap/p2p/bitswap/sample_block.go index 0984b06827..3c6a341cad 100644 --- a/share/shwap/p2p/bitswap/sample_block.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -6,12 +6,10 @@ import ( "github.com/ipfs/go-cid" - eds "github.com/celestiaorg/celestia-node/share/new_eds" - - shwappb "github.com/celestiaorg/celestia-node/share/shwap/pb" - "github.com/celestiaorg/celestia-node/share" + eds "github.com/celestiaorg/celestia-node/share/new_eds" "github.com/celestiaorg/celestia-node/share/shwap" + shwappb "github.com/celestiaorg/celestia-node/share/shwap/pb" ) const ( diff --git a/share/shwap/p2p/bitswap/sample_block_test.go b/share/shwap/p2p/bitswap/sample_block_test.go index fdc5517eeb..b6ae552722 100644 --- a/share/shwap/p2p/bitswap/sample_block_test.go +++ b/share/shwap/p2p/bitswap/sample_block_test.go @@ -12,10 +12,10 @@ import ( ) func TestSampleRoundtrip_GetContainers(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*50) defer cancel() - eds := edstest.RandEDS(t, 16) + eds := edstest.RandEDS(t, 32) root, err := share.NewRoot(eds) require.NoError(t, err) client := fetcher(ctx, t, eds) @@ -30,13 +30,9 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { } } - // NOTE: this the limiting number of items bitswap can do in a single Fetch - // going beyond that cause Bitswap to stall indefinitely - const maxPerFetch = 1024 - for i := range len(blks) / maxPerFetch { - err = Fetch(ctx, client, root, blks[i*maxPerFetch:(i+1)*maxPerFetch]...) - require.NoError(t, err) - } + ctx = WithFetchSession(ctx, client) + err = Fetch(ctx, client, root, blks...) + require.NoError(t, err) for _, sample := range blks { blk := sample.(*SampleBlock) From 491163dc06c9c5acbd2181c6359193fc2c0f1858 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Thu, 20 Jun 2024 19:46:30 +0200 Subject: [PATCH 27/40] pb comment --- share/shwap/p2p/bitswap/pb/bitswap.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/share/shwap/p2p/bitswap/pb/bitswap.proto b/share/shwap/p2p/bitswap/pb/bitswap.proto index aa06fbf5b5..3ba19aa49c 100644 --- a/share/shwap/p2p/bitswap/pb/bitswap.proto +++ b/share/shwap/p2p/bitswap/pb/bitswap.proto @@ -1,3 +1,4 @@ +// Defined in CIP-19 https://github.com/celestiaorg/CIPs/blob/82aeb7dfc472105a11babffd548c730c899a3d24/cips/cip-19.md syntax = "proto3"; package bitswap; option go_package = "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb"; From 502dbaebe64ac1deab9bbf3d656b218121d24402 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Thu, 20 Jun 2024 20:05:00 +0200 Subject: [PATCH 28/40] improve docs, logging and errors --- share/shwap/p2p/bitswap/block_fetch.go | 5 ++--- share/shwap/p2p/bitswap/block_registry.go | 5 +++++ share/shwap/p2p/bitswap/block_store.go | 24 ++++++++++++++++++----- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index d741f89c5a..0cdd4ed85b 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -50,7 +50,6 @@ const maxPerFetch = 1024 // See [Fetch] for detailed description. func fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks ...Block) error { fetcher := getFetcher(ctx, exchg) - cids := make([]cid.Cid, 0, len(blks)) duplicates := make(map[cid.Cid]Block) for _, blk := range blks { @@ -122,14 +121,14 @@ func unmarshal(unmarshalFn UnmarshalFn, data []byte) ([]byte, error) { if err != nil { return nil, fmt.Errorf("casting cid: %w", err) } - // get ID out of CID validating it + // getBlock ID out of CID validating it id, err := extractCID(cid) if err != nil { return nil, fmt.Errorf("validating cid: %w", err) } if unmarshalFn == nil { - // get registered UnmarshalFn and use it to check data validity and + // getBlock registered UnmarshalFn and use it to check data validity and // pass it to Fetch caller val, ok := unmarshalFns.Load(cid) if !ok { diff --git a/share/shwap/p2p/bitswap/block_registry.go b/share/shwap/p2p/bitswap/block_registry.go index 0b0d7bd1f4..bdf10d0861 100644 --- a/share/shwap/p2p/bitswap/block_registry.go +++ b/share/shwap/p2p/bitswap/block_registry.go @@ -1,6 +1,7 @@ package bitswap import ( + "fmt" "hash" "github.com/ipfs/go-cid" @@ -26,4 +27,8 @@ type blockSpec struct { builder func(cid.Cid) (Block, error) } +func (spec *blockSpec) String() string { + return fmt.Sprintf("BlockSpec{size: %d, codec: %d}", spec.size, spec.codec) +} + var specRegistry = make(map[uint64]blockSpec) diff --git a/share/shwap/p2p/bitswap/block_store.go b/share/shwap/p2p/bitswap/block_store.go index 72994301d5..9a5418054d 100644 --- a/share/shwap/p2p/bitswap/block_store.go +++ b/share/shwap/p2p/bitswap/block_store.go @@ -11,32 +11,36 @@ import ( bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" ) +// Accessors abstracts storage system that indexes and manages multiple eds.Accessors by network height. type Accessors interface { + // Get returns an Accessor by its height. Get(ctx context.Context, height uint64) (eds.Accessor, error) } +// Blockstore implements generalized Bitswap compatible storage over Shwap containers +// that operates with Block and accesses data through Accessors. type Blockstore struct { Accessors } -func (b *Blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { +func (b *Blockstore) getBlock(ctx context.Context, cid cid.Cid) (blocks.Block, error) { spec, ok := specRegistry[cid.Prefix().MhType] if !ok { - return nil, fmt.Errorf("unsupported codec") + return nil, fmt.Errorf("unsupported Block type: %v", cid.Prefix().MhType) } blk, err := spec.builder(cid) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to build a Block for %s: %w", spec.String(), err) } eds, err := b.Accessors.Get(ctx, blk.Height()) if err != nil { - return nil, err + return nil, fmt.Errorf("getting EDS Accessor for height %v: %w", blk.Height(), err) } if err = blk.Populate(ctx, eds); err != nil { - return nil, fmt.Errorf("failed to unmarshal Shwap Block: %w", err) + return nil, fmt.Errorf("failed to populate Shwap Block on height %v for %s: %w", blk.Height(), spec.String(), err) } containerData, err := blk.Marshal() @@ -62,6 +66,16 @@ func (b *Blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) return bitswapBlk, nil } +func (b *Blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { + blk, err := b.getBlock(ctx, cid) + if err != nil { + log.Errorf("blockstore: getting local block(%s): %s", cid, err) + return nil, err + } + + return blk, nil +} + func (b *Blockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { // TODO(@Wondertan): There must be a way to derive size without reading, proving, serializing and // allocating Sample's block.Block or we could do hashing From 3e8ac7ba53409c0916fd08563cb7615181895fa1 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Thu, 20 Jun 2024 20:07:50 +0200 Subject: [PATCH 29/40] micro fix --- share/shwap/p2p/bitswap/block.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/shwap/p2p/bitswap/block.go b/share/shwap/p2p/bitswap/block.go index 8b3160db7d..9a1678c6d7 100644 --- a/share/shwap/p2p/bitswap/block.go +++ b/share/shwap/p2p/bitswap/block.go @@ -15,7 +15,7 @@ var log = logger.Logger("shwap/bitswap") // Block represents Bitswap compatible Shwap container. // All Shwap containers must have a registerBlock-ed wrapper // implementing the interface to be compatible with Bitswap. -// NOTE: This is not Blockchain block, but IPFS/Bitswap block/ +// NOTE: This is not Blockchain block, but IPFS/Bitswap block. type Block interface { // CID returns Shwap ID of the Block formatted as CID. CID() cid.Cid From 297e7263e3c9d2041c739f19ae7a0957326ab2a4 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Thu, 20 Jun 2024 20:56:55 +0200 Subject: [PATCH 30/40] improve comments --- share/shwap/p2p/bitswap/block.go | 20 ++++++++++---------- share/shwap/p2p/bitswap/block_registry.go | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/share/shwap/p2p/bitswap/block.go b/share/shwap/p2p/bitswap/block.go index 9a1678c6d7..6cb3c22a35 100644 --- a/share/shwap/p2p/bitswap/block.go +++ b/share/shwap/p2p/bitswap/block.go @@ -12,14 +12,14 @@ import ( var log = logger.Logger("shwap/bitswap") -// Block represents Bitswap compatible Shwap container. -// All Shwap containers must have a registerBlock-ed wrapper -// implementing the interface to be compatible with Bitswap. -// NOTE: This is not Blockchain block, but IPFS/Bitswap block. +// Block represents Bitswap compatible generalization over Shwap containers. +// All Shwap containers must have a registered wrapper +// implementing the interface in order to be compatible with Bitswap. +// NOTE: This is not a Blockchain block, but an IPFS/Bitswap block. type Block interface { // CID returns Shwap ID of the Block formatted as CID. CID() cid.Cid - // Height reports the Height the Shwap Container data behind the Block is from. + // Height reports the Height of the Shwap container behind the Block. Height() uint64 // IsEmpty reports whether the Block holds respective Shwap container. @@ -27,14 +27,14 @@ type Block interface { // Populate fills up the Block with the Shwap container getting it out of the EDS // Accessor. Populate(context.Context, eds.Accessor) error - // Marshal serializes bytes of Shwap Container the Block holds. - // Must not include the Shwap ID. + // Marshal serializes bytes of the Shwap Container the Block holds. + // MUST exclude the Shwap ID. Marshal() ([]byte, error) - // UnmarshalFn returns closure that unmarshal the Block with Shwap container. - // Unmarshalling involves data validation against the Root. + // UnmarshalFn returns closure that unmarshal the Block with the Shwap container. + // Unmarshalling involves data validation against the given Root. UnmarshalFn(*share.Root) UnmarshalFn } // UnmarshalFn is a closure produced by a Block that unmarshalls and validates -// given serialized Shwap container and populates the Block with it on success. +// the given serialized bytes of a Shwap container and populates the Block with it on success. type UnmarshalFn func([]byte) error diff --git a/share/shwap/p2p/bitswap/block_registry.go b/share/shwap/p2p/bitswap/block_registry.go index bdf10d0861..b547e37859 100644 --- a/share/shwap/p2p/bitswap/block_registry.go +++ b/share/shwap/p2p/bitswap/block_registry.go @@ -20,7 +20,7 @@ func registerBlock(mhcode, codec uint64, size int, bldrFn func(cid.Cid) (Block, } } -// blockSpec holds +// blockSpec holds constant metadata about particular Block types. type blockSpec struct { size int codec uint64 From 2b13d156cb7e451cfe4256bdf07a8d515e56b40e Mon Sep 17 00:00:00 2001 From: Wondertan Date: Mon, 24 Jun 2024 21:20:43 +0200 Subject: [PATCH 31/40] review comments --- share/shwap/p2p/bitswap/block_fetch.go | 4 ++++ share/shwap/p2p/bitswap/block_fetch_test.go | 6 +++--- share/shwap/p2p/bitswap/block_store.go | 14 +++++++------- share/shwap/p2p/bitswap/sample_block_test.go | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 0cdd4ed85b..eac07360f6 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -136,6 +136,9 @@ func unmarshal(unmarshalFn UnmarshalFn, data []byte) ([]byte, error) { } entry := val.(*unmarshalEntry) + // ensure UnmarshalFn is synchronized + // NOTE: Bitswap may call hasher.Write concurrently, which may call unmarshall concurrently + // this we need this synchronization. entry.Lock() defer entry.Unlock() unmarshalFn = entry.UnmarshalFn @@ -162,6 +165,7 @@ func unmarshal(unmarshalFn UnmarshalFn, data []byte) ([]byte, error) { // sync.Map is used to minimize contention for disjoint keys var unmarshalFns sync.Map +// unmarshalEntry wraps UnmarshalFn with a mutex to protect it from concurrent access. type unmarshalEntry struct { sync.Mutex UnmarshalFn diff --git a/share/shwap/p2p/bitswap/block_fetch_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go index f3116ca7dc..263dc47493 100644 --- a/share/shwap/p2p/bitswap/block_fetch_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -73,7 +73,7 @@ func TestFetchDuplicates(t *testing.T) { func fetcher(ctx context.Context, t *testing.T, rsmt2dEds *rsmt2d.ExtendedDataSquare) exchange.SessionExchange { bstore := &Blockstore{ - Accessors: testAccessors{ + Getter: testAccessorGetter{ Accessor: eds.Rsmt2D{ExtendedDataSquare: rsmt2dEds}, }, } @@ -120,10 +120,10 @@ func newClient(ctx context.Context, host host.Host, store blockstore.Blockstore) return client } -type testAccessors struct { +type testAccessorGetter struct { eds.Accessor } -func (t testAccessors) Get(context.Context, uint64) (eds.Accessor, error) { +func (t testAccessorGetter) GetByHeight(context.Context, uint64) (eds.Accessor, error) { return t.Accessor, nil } diff --git a/share/shwap/p2p/bitswap/block_store.go b/share/shwap/p2p/bitswap/block_store.go index 9a5418054d..fa0a6c79f3 100644 --- a/share/shwap/p2p/bitswap/block_store.go +++ b/share/shwap/p2p/bitswap/block_store.go @@ -11,16 +11,16 @@ import ( bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" ) -// Accessors abstracts storage system that indexes and manages multiple eds.Accessors by network height. -type Accessors interface { - // Get returns an Accessor by its height. - Get(ctx context.Context, height uint64) (eds.Accessor, error) +// AccessorGetter abstracts storage system that indexes and manages multiple eds.AccessorGetter by network height. +type AccessorGetter interface { + // GetByHeight returns an Accessor by its height. + GetByHeight(ctx context.Context, height uint64) (eds.Accessor, error) } // Blockstore implements generalized Bitswap compatible storage over Shwap containers -// that operates with Block and accesses data through Accessors. +// that operates with Block and accesses data through AccessorGetter. type Blockstore struct { - Accessors + Getter AccessorGetter } func (b *Blockstore) getBlock(ctx context.Context, cid cid.Cid) (blocks.Block, error) { @@ -34,7 +34,7 @@ func (b *Blockstore) getBlock(ctx context.Context, cid cid.Cid) (blocks.Block, e return nil, fmt.Errorf("failed to build a Block for %s: %w", spec.String(), err) } - eds, err := b.Accessors.Get(ctx, blk.Height()) + eds, err := b.Getter.GetByHeight(ctx, blk.Height()) if err != nil { return nil, fmt.Errorf("getting EDS Accessor for height %v: %w", blk.Height(), err) } diff --git a/share/shwap/p2p/bitswap/sample_block_test.go b/share/shwap/p2p/bitswap/sample_block_test.go index b6ae552722..7e687e1d26 100644 --- a/share/shwap/p2p/bitswap/sample_block_test.go +++ b/share/shwap/p2p/bitswap/sample_block_test.go @@ -12,7 +12,7 @@ import ( ) func TestSampleRoundtrip_GetContainers(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*50) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() eds := edstest.RandEDS(t, 32) From 9459c89ae189c482c76eb5e8909849574219de06 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Mon, 24 Jun 2024 21:22:17 +0200 Subject: [PATCH 32/40] avoid nolint directive --- share/shwap/p2p/bitswap/block_fetch.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index eac07360f6..1a5ad73637 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -26,7 +26,8 @@ func WithFetchSession(ctx context.Context, exchg exchange.SessionExchange) conte // Gracefully synchronize identical Blocks requested simultaneously. // Blocks until either context is canceled or all Blocks are fetched and populated. func Fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks ...Block) error { - for from, to := 0, 0; to < len(blks); { //nolint:wastedassign // it's not actually wasted + var from, to int + for to < len(blks) { from, to = to, to+maxPerFetch if to >= len(blks) { to = len(blks) From c947ce83041476a7cd6d7cb2daa13d4085213580 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Mon, 24 Jun 2024 21:39:56 +0200 Subject: [PATCH 33/40] verify blocks aren't empty --- share/shwap/p2p/bitswap/block_fetch.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 1a5ad73637..2b473d5e8c 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -54,10 +54,6 @@ func fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks cids := make([]cid.Cid, 0, len(blks)) duplicates := make(map[cid.Cid]Block) for _, blk := range blks { - if !blk.IsEmpty() { - continue // skip populated Blocks - } - cid := blk.CID() // memoize CID for reuse as it ain't free cids = append(cids, cid) @@ -106,8 +102,20 @@ func fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks // problem, but they are *much* more complex. Considering this a rare edge-case the tradeoff // towards simplicity has been made. } + if ctx.Err() != nil { + return ctx.Err() + } - return ctx.Err() + for _, blk := range blks { + if blk.IsEmpty() { + // NOTE: This check verifies that Bitswap did it job correctly and gave us everything + // requested. If there is still an empty block somewhere this suggests there is a bug + // on the intersection of Bitswap and Fetch function. + return fmt.Errorf("got empty block from Bitswap: %s", blk.CID()) + } + } + + return nil } // unmarshal unmarshalls the Shwap Container data into a Block via UnmarshalFn From ff00d94c2fcda1971432f52ee6f5309e0f6be40c Mon Sep 17 00:00:00 2001 From: Wondertan Date: Mon, 24 Jun 2024 22:33:08 +0200 Subject: [PATCH 34/40] WithFetcher and WithStore options --- share/shwap/p2p/bitswap/block_fetch.go | 96 ++++++++++++------- share/shwap/p2p/bitswap/block_fetch_test.go | 60 +++++++++++- share/shwap/p2p/bitswap/row_block_test.go | 4 +- .../bitswap/row_namespace_data_block_test.go | 4 +- share/shwap/p2p/bitswap/sample_block_test.go | 5 +- 5 files changed, 126 insertions(+), 43 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 2b473d5e8c..4e7a733e46 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -6,18 +6,28 @@ import ( "fmt" "sync" + "github.com/ipfs/boxo/blockstore" "github.com/ipfs/boxo/exchange" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" "github.com/celestiaorg/celestia-node/share" bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" ) -// WithFetchSession instantiates a new Fetch session and saves it on the context. -// Session reuses peers known to have Blocks without rediscovering. -func WithFetchSession(ctx context.Context, exchg exchange.SessionExchange) context.Context { - fetcher := exchg.NewSession(ctx) - return context.WithValue(ctx, fetcherKey, fetcher) +// WithFetcher instructs [Fetch] to use the given Fetcher. +// Useful for reusable Fetcher sessions. +func WithFetcher(session exchange.Fetcher) FetchOption { + return func(options *fetchOptions) { + options.Session = session + } +} + +// WithStore instructs [Fetch] to store all the fetched Blocks into the given Blockstore. +func WithStore(store blockstore.Blockstore) FetchOption { + return func(options *fetchOptions) { + options.Store = store + } } // Fetch fetches and populates given Blocks using Fetcher wrapping Bitswap. @@ -25,7 +35,7 @@ func WithFetchSession(ctx context.Context, exchg exchange.SessionExchange) conte // Validates Block against the given Root and skips Blocks that are already populated. // Gracefully synchronize identical Blocks requested simultaneously. // Blocks until either context is canceled or all Blocks are fetched and populated. -func Fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks ...Block) error { +func Fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks []Block, opts ...FetchOption) error { var from, to int for to < len(blks) { from, to = to, to+maxPerFetch @@ -33,7 +43,7 @@ func Fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks to = len(blks) } - err := fetch(ctx, exchg, root, blks[from:to]...) + err := fetch(ctx, exchg, root, blks[from:to], opts...) if err != nil { return err } @@ -49,8 +59,13 @@ const maxPerFetch = 1024 // fetch fetches given Blocks. // See [Fetch] for detailed description. -func fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks ...Block) error { - fetcher := getFetcher(ctx, exchg) +func fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks []Block, opts ...FetchOption) error { + var options fetchOptions + for _, opt := range opts { + opt(&options) + } + + fetcher := options.getFetcher(exchg) cids := make([]cid.Cid, 0, len(blks)) duplicates := make(map[cid.Cid]Block) for _, blk := range blks { @@ -78,29 +93,36 @@ func fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks } for bitswapBlk := range blkCh { // GetBlocks closes blkCh on ctx cancellation + // NOTE: notification for duplicates is on purpose and to cover a flaky case + // It's harmless in practice to do additional notifications in case of duplicates if err := exchg.NotifyNewBlocks(ctx, bitswapBlk); err != nil { - log.Error("failed to notify new Bitswap blocks: %w", err) + log.Error("failed to notify the new Bitswap block: %s", err) } blk, ok := duplicates[bitswapBlk.Cid()] - if !ok { - // common case: the block was populated by the hasher, so skip + if ok { + // uncommon duplicate case: concurrent fetching of the same block, + // so we have to unmarshal it ourselves instead of the hasher, + unmarshalFn := blk.UnmarshalFn(root) + _, err := unmarshal(unmarshalFn, bitswapBlk.RawData()) + if err != nil { + // this means verification succeeded in the hasher but failed here + // this case should never happen in practice + // and if so something is really wrong + panic(fmt.Sprintf("unmarshaling duplicate block: %s", err)) + } + // NOTE: This approach has a downside that we redo deserialization and computationally + // expensive computation for as many duplicates. We tried solutions that doesn't have this + // problem, but they are *much* more complex. Considering this a rare edge-case the tradeoff + // towards simplicity has been made. continue } - // uncommon duplicate case: concurrent fetching of the same block, - // so we have to unmarshal it ourselves instead of the hasher, - unmarshalFn := blk.UnmarshalFn(root) - _, err := unmarshal(unmarshalFn, bitswapBlk.RawData()) + // common case: the block was populated by the hasher + // so store it if requested + err := options.store(ctx, bitswapBlk) if err != nil { - // this means verification succeeded in the hasher but failed here - // this case should never happen in practice - // and if so something is really wrong - panic(fmt.Sprintf("unmarshaling duplicate block: %s", err)) + log.Error("failed to store the new Bitswap block: %s", err) } - // NOTE: This approach has a downside that we redo deserialization and computationally - // expensive computation for as many duplicates. We tried solutions that doesn't have this - // problem, but they are *much* more complex. Considering this a rare edge-case the tradeoff - // towards simplicity has been made. } if ctx.Err() != nil { return ctx.Err() @@ -219,17 +241,25 @@ func (h *hasher) BlockSize() int { return sha256.BlockSize } -type fetcherSessionKey struct{} +type FetchOption func(*fetchOptions) + +type fetchOptions struct { + Session exchange.Fetcher + Store blockstore.Blockstore +} + +func (options *fetchOptions) getFetcher(exhng exchange.Interface) exchange.Fetcher { + if options.Session != nil { + return options.Session + } -var fetcherKey fetcherSessionKey + return exhng +} -// getFetcher takes context and a fetcher, if there is another fetcher in the context, -// it gets returned. -func getFetcher(ctx context.Context, fetcher exchange.Fetcher) exchange.Fetcher { - fetcherVal := ctx.Value(fetcherKey) - if fetcherVal == nil { - return fetcher +func (options *fetchOptions) store(ctx context.Context, blk blocks.Block) error { + if options.Store == nil { + return nil } - return fetcherVal.(exchange.Fetcher) + return options.Store.Put(ctx, blk) } diff --git a/share/shwap/p2p/bitswap/block_fetch_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go index 263dc47493..53b6ae1033 100644 --- a/share/shwap/p2p/bitswap/block_fetch_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -13,6 +13,8 @@ import ( "github.com/ipfs/boxo/blockstore" "github.com/ipfs/boxo/exchange" "github.com/ipfs/boxo/routing/offline" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" record "github.com/libp2p/go-libp2p-record" @@ -28,6 +30,43 @@ import ( eds "github.com/celestiaorg/celestia-node/share/new_eds" ) +func TestFetchOptions(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) + defer cancel() + + eds := edstest.RandEDS(t, 4) + root, err := share.NewRoot(eds) + require.NoError(t, err) + exchange := newExchange(ctx, t, eds) + + blks := make([]Block, eds.Width()) + for i := range blks { + blk, err := NewEmptyRowBlock(1, i, root) // create the same Block ID + require.NoError(t, err) + blks[i] = blk + } + + t.Run("WithBlockstore", func(t *testing.T) { + bstore := blockstore.NewBlockstore(ds.NewMapDatastore()) + err := Fetch(ctx, exchange, root, blks, WithStore(bstore)) + require.NoError(t, err) + + for _, blk := range blks { + ok, err := bstore.Has(ctx, blk.CID()) + require.NoError(t, err) + require.True(t, ok) + } + }) + + t.Run("WithFetcher", func(t *testing.T) { + session := exchange.NewSession(ctx) + fetcher := &testFetcher{Embedded: session} + err := Fetch(ctx, exchange, root, blks, WithFetcher(fetcher)) + require.NoError(t, err) + require.Equal(t, len(blks), fetcher.Fetched) + }) +} + func TestFetchDuplicates(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) defer cancel() @@ -35,7 +74,7 @@ func TestFetchDuplicates(t *testing.T) { eds := edstest.RandEDS(t, 4) root, err := share.NewRoot(eds) require.NoError(t, err) - fetcher := fetcher(ctx, t, eds) + exchange := newExchange(ctx, t, eds) var wg sync.WaitGroup for i := range 100 { @@ -52,7 +91,7 @@ func TestFetchDuplicates(t *testing.T) { // this sleep ensures fetches aren't started simultaneously, allowing to check for edge-cases time.Sleep(time.Millisecond * time.Duration(rint)) - err := Fetch(ctx, fetcher, root, blks...) + err := Fetch(ctx, exchange, root, blks) assert.NoError(t, err) for _, blk := range blks { assert.False(t, blk.IsEmpty()) @@ -71,7 +110,7 @@ func TestFetchDuplicates(t *testing.T) { require.Zero(t, entries) } -func fetcher(ctx context.Context, t *testing.T, rsmt2dEds *rsmt2d.ExtendedDataSquare) exchange.SessionExchange { +func newExchange(ctx context.Context, t *testing.T, rsmt2dEds *rsmt2d.ExtendedDataSquare) exchange.SessionExchange { bstore := &Blockstore{ Getter: testAccessorGetter{ Accessor: eds.Rsmt2D{ExtendedDataSquare: rsmt2dEds}, @@ -127,3 +166,18 @@ type testAccessorGetter struct { func (t testAccessorGetter) GetByHeight(context.Context, uint64) (eds.Accessor, error) { return t.Accessor, nil } + +type testFetcher struct { + Fetched int + + Embedded exchange.Fetcher +} + +func (t *testFetcher) GetBlock(context.Context, cid.Cid) (blocks.Block, error) { + panic("not implemented") +} + +func (t *testFetcher) GetBlocks(ctx context.Context, cids []cid.Cid) (<-chan blocks.Block, error) { + t.Fetched += len(cids) + return t.Embedded.GetBlocks(ctx, cids) +} diff --git a/share/shwap/p2p/bitswap/row_block_test.go b/share/shwap/p2p/bitswap/row_block_test.go index 27c6b12f16..85d71717a5 100644 --- a/share/shwap/p2p/bitswap/row_block_test.go +++ b/share/shwap/p2p/bitswap/row_block_test.go @@ -18,7 +18,7 @@ func TestRowRoundtrip_GetContainers(t *testing.T) { eds := edstest.RandEDS(t, 4) root, err := share.NewRoot(eds) require.NoError(t, err) - client := fetcher(ctx, t, eds) + exchange := newExchange(ctx, t, eds) blks := make([]Block, eds.Width()) for i := range blks { @@ -27,7 +27,7 @@ func TestRowRoundtrip_GetContainers(t *testing.T) { blks[i] = blk } - err = Fetch(ctx, client, root, blks...) + err = Fetch(ctx, exchange, root, blks) require.NoError(t, err) for _, blk := range blks { diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block_test.go b/share/shwap/p2p/bitswap/row_namespace_data_block_test.go index fd538fc925..a4a878cf7b 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block_test.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block_test.go @@ -18,7 +18,7 @@ func TestRowNamespaceDataRoundtrip_GetContainers(t *testing.T) { namespace := sharetest.RandV0Namespace() eds, root := edstest.RandEDSWithNamespace(t, namespace, 64, 16) - client := fetcher(ctx, t, eds) + exchange := newExchange(ctx, t, eds) rowIdxs := share.RowsWithNamespace(root, namespace) blks := make([]Block, len(rowIdxs)) @@ -28,7 +28,7 @@ func TestRowNamespaceDataRoundtrip_GetContainers(t *testing.T) { blks[i] = blk } - err := Fetch(ctx, client, root, blks...) + err := Fetch(ctx, exchange, root, blks) require.NoError(t, err) for _, blk := range blks { diff --git a/share/shwap/p2p/bitswap/sample_block_test.go b/share/shwap/p2p/bitswap/sample_block_test.go index 7e687e1d26..ccea1eb4cc 100644 --- a/share/shwap/p2p/bitswap/sample_block_test.go +++ b/share/shwap/p2p/bitswap/sample_block_test.go @@ -18,7 +18,7 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { eds := edstest.RandEDS(t, 32) root, err := share.NewRoot(eds) require.NoError(t, err) - client := fetcher(ctx, t, eds) + exchange := newExchange(ctx, t, eds) width := int(eds.Width()) blks := make([]Block, 0, width*width) @@ -30,8 +30,7 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { } } - ctx = WithFetchSession(ctx, client) - err = Fetch(ctx, client, root, blks...) + err = Fetch(ctx, exchange, root, blks) require.NoError(t, err) for _, sample := range blks { From 77fc9795349dff4015939b8ab265529b6875ec42 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Mon, 24 Jun 2024 23:12:04 +0200 Subject: [PATCH 35/40] improve comment --- share/shwap/p2p/bitswap/block_fetch.go | 4 ++-- share/shwap/p2p/bitswap/sample_block_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 4e7a733e46..44dc0dc484 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -53,8 +53,8 @@ func Fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks } // maxPerFetch sets the limit for maximum items in a single fetch. -// It's a heuristic coming from Bitswap, which apparently can't process more than ~1024 in a single -// GetBlock call. Going beyond that stalls the call indefinitely. +// This limit comes from server side default limit size on max possible simultaneous CID WANTs from a peer. +// https://github.com/ipfs/boxo/blob/dfd4a53ba828a368cec8d61c3fe12969ac6aa94c/bitswap/internal/defaults/defaults.go#L29-L30 const maxPerFetch = 1024 // fetch fetches given Blocks. diff --git a/share/shwap/p2p/bitswap/sample_block_test.go b/share/shwap/p2p/bitswap/sample_block_test.go index ccea1eb4cc..0496d53be0 100644 --- a/share/shwap/p2p/bitswap/sample_block_test.go +++ b/share/shwap/p2p/bitswap/sample_block_test.go @@ -12,7 +12,7 @@ import ( ) func TestSampleRoundtrip_GetContainers(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute+time.Second*10) defer cancel() eds := edstest.RandEDS(t, 32) @@ -30,7 +30,7 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { } } - err = Fetch(ctx, exchange, root, blks) + err = fetch(ctx, exchange, root, blks) require.NoError(t, err) for _, sample := range blks { From b1d7285b73fff6772918e78ad469cc40e574c03c Mon Sep 17 00:00:00 2001 From: Wondertan Date: Tue, 25 Jun 2024 16:37:39 +0200 Subject: [PATCH 36/40] untagle Block unmarshalling and extract proto funcs in a separate file --- share/shwap/p2p/bitswap/block_fetch.go | 83 +++++++++++--------- share/shwap/p2p/bitswap/block_proto.go | 46 +++++++++++ share/shwap/p2p/bitswap/block_store.go | 17 +--- share/shwap/p2p/bitswap/sample_block_test.go | 4 +- 4 files changed, 95 insertions(+), 55 deletions(-) create mode 100644 share/shwap/p2p/bitswap/block_proto.go diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 44dc0dc484..7df4fa2be7 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -12,7 +12,6 @@ import ( "github.com/ipfs/go-cid" "github.com/celestiaorg/celestia-node/share" - bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" ) // WithFetcher instructs [Fetch] to use the given Fetcher. @@ -104,7 +103,7 @@ func fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks // uncommon duplicate case: concurrent fetching of the same block, // so we have to unmarshal it ourselves instead of the hasher, unmarshalFn := blk.UnmarshalFn(root) - _, err := unmarshal(unmarshalFn, bitswapBlk.RawData()) + err := unmarshal(unmarshalFn, bitswapBlk.RawData()) if err != nil { // this means verification succeeded in the hasher but failed here // this case should never happen in practice @@ -140,47 +139,19 @@ func fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks return nil } -// unmarshal unmarshalls the Shwap Container data into a Block via UnmarshalFn -// If unmarshalFn is nil -- gets it from the global unmarshalFns. -func unmarshal(unmarshalFn UnmarshalFn, data []byte) ([]byte, error) { - var blk bitswappb.Block - err := blk.Unmarshal(data) +// unmarshal unmarshalls the Shwap Container data into a Block with the given UnmarshalFn +func unmarshal(unmarshalFn UnmarshalFn, data []byte) error { + _, containerData, err := unmarshalProto(data) if err != nil { - return nil, fmt.Errorf("unmarshalling block: %w", err) - } - cid, err := cid.Cast(blk.Cid) - if err != nil { - return nil, fmt.Errorf("casting cid: %w", err) - } - // getBlock ID out of CID validating it - id, err := extractCID(cid) - if err != nil { - return nil, fmt.Errorf("validating cid: %w", err) - } - - if unmarshalFn == nil { - // getBlock registered UnmarshalFn and use it to check data validity and - // pass it to Fetch caller - val, ok := unmarshalFns.Load(cid) - if !ok { - return nil, fmt.Errorf("no unmarshallers registered for %s", cid.String()) - } - entry := val.(*unmarshalEntry) - - // ensure UnmarshalFn is synchronized - // NOTE: Bitswap may call hasher.Write concurrently, which may call unmarshall concurrently - // this we need this synchronization. - entry.Lock() - defer entry.Unlock() - unmarshalFn = entry.UnmarshalFn + return err } - err = unmarshalFn(blk.Container) + err = unmarshalFn(containerData) if err != nil { - return nil, fmt.Errorf("verifying data: %w", err) + return fmt.Errorf("verifying and unmarshalling container data: %w", err) } - return id, nil + return nil } // unmarshalFns exist to communicate between Fetch and hasher, and it's global as a necessity @@ -212,17 +183,51 @@ type hasher struct { } func (h *hasher) Write(data []byte) (int, error) { - id, err := unmarshal(nil, data) + err := h.write(data) if err != nil { err = fmt.Errorf("hasher: %w", err) log.Error(err) return 0, fmt.Errorf("shwap/bitswap: %w", err) } + + return len(data), nil +} + +func (h *hasher) write(data []byte) error { + cid, container, err := unmarshalProto(data) + if err != nil { + return fmt.Errorf("unmarshalling proto: %w", err) + } + + // getBlock ID out of CID validating it + id, err := extractCID(cid) + if err != nil { + return fmt.Errorf("validating cid: %w", err) + } + + // getBlock registered UnmarshalFn and use it to check data validity and + // pass it to Fetch caller + val, ok := unmarshalFns.Load(cid) + if !ok { + return fmt.Errorf("no unmarshallers registered for %s", cid.String()) + } + entry := val.(*unmarshalEntry) + + // ensure UnmarshalFn is synchronized + // NOTE: Bitswap may call hasher.Write concurrently, which may call unmarshall concurrently + // this we need this synchronization. + entry.Lock() + err = entry.UnmarshalFn(container) + if err != nil { + return fmt.Errorf("verifying and unmarshalling container data: %w", err) + } + entry.Unlock() + // set the id as resulting sum // it's required for the sum to match the requested ID // to satisfy hash contract and signal to Bitswap that data is correct h.sum = id - return len(data), err + return nil } func (h *hasher) Sum([]byte) []byte { diff --git a/share/shwap/p2p/bitswap/block_proto.go b/share/shwap/p2p/bitswap/block_proto.go new file mode 100644 index 0000000000..8cab645705 --- /dev/null +++ b/share/shwap/p2p/bitswap/block_proto.go @@ -0,0 +1,46 @@ +package bitswap + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" +) + +// marshalProto wraps the given Block in composition protobuf and marshals it. +func marshalProto(blk Block) ([]byte, error) { + containerData, err := blk.Marshal() + if err != nil { + return nil, fmt.Errorf("marshaling Shwap container: %w", err) + } + + blkProto := bitswappb.Block{ + Cid: blk.CID().Bytes(), + Container: containerData, + } + + blkData, err := blkProto.Marshal() + if err != nil { + return nil, fmt.Errorf("marshaling Bitswap Block protobuf: %w", err) + } + + return blkData, nil +} + +// unmarshalProto unwraps given data from composition protobuf and provides +// inner CID and serialized container data. +func unmarshalProto(data []byte) (cid.Cid, []byte, error) { + var blk bitswappb.Block + err := blk.Unmarshal(data) + if err != nil { + return cid.Undef, nil, fmt.Errorf("unmarshalling protobuf block: %w", err) + } + + cid, err := cid.Cast(blk.Cid) + if err != nil { + return cid, nil, fmt.Errorf("casting cid: %w", err) + } + + return cid, blk.Container, nil +} diff --git a/share/shwap/p2p/bitswap/block_store.go b/share/shwap/p2p/bitswap/block_store.go index fa0a6c79f3..700d8798c7 100644 --- a/share/shwap/p2p/bitswap/block_store.go +++ b/share/shwap/p2p/bitswap/block_store.go @@ -8,7 +8,6 @@ import ( "github.com/ipfs/go-cid" eds "github.com/celestiaorg/celestia-node/share/new_eds" - bitswappb "github.com/celestiaorg/celestia-node/share/shwap/p2p/bitswap/pb" ) // AccessorGetter abstracts storage system that indexes and manages multiple eds.AccessorGetter by network height. @@ -43,22 +42,12 @@ func (b *Blockstore) getBlock(ctx context.Context, cid cid.Cid) (blocks.Block, e return nil, fmt.Errorf("failed to populate Shwap Block on height %v for %s: %w", blk.Height(), spec.String(), err) } - containerData, err := blk.Marshal() + protoData, err := marshalProto(blk) if err != nil { - return nil, fmt.Errorf("marshaling Shwap container: %w", err) + return nil, fmt.Errorf("failed to wrap Block with proto: %w", err) } - blkProto := bitswappb.Block{ - Cid: cid.Bytes(), - Container: containerData, - } - - blkData, err := blkProto.Marshal() - if err != nil { - return nil, fmt.Errorf("marshaling Bitswap Block protobuf: %w", err) - } - - bitswapBlk, err := blocks.NewBlockWithCid(blkData, cid) + bitswapBlk, err := blocks.NewBlockWithCid(protoData, cid) if err != nil { return nil, fmt.Errorf("assembling Bitswap block: %w", err) } diff --git a/share/shwap/p2p/bitswap/sample_block_test.go b/share/shwap/p2p/bitswap/sample_block_test.go index 0496d53be0..ccea1eb4cc 100644 --- a/share/shwap/p2p/bitswap/sample_block_test.go +++ b/share/shwap/p2p/bitswap/sample_block_test.go @@ -12,7 +12,7 @@ import ( ) func TestSampleRoundtrip_GetContainers(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute+time.Second*10) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() eds := edstest.RandEDS(t, 32) @@ -30,7 +30,7 @@ func TestSampleRoundtrip_GetContainers(t *testing.T) { } } - err = fetch(ctx, exchange, root, blks) + err = Fetch(ctx, exchange, root, blks) require.NoError(t, err) for _, sample := range blks { From 99821a93b275cc628788fb99ed2691e8dbab1a29 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Tue, 25 Jun 2024 17:13:48 +0200 Subject: [PATCH 37/40] report number of empty blocks --- share/shwap/p2p/bitswap/block_fetch.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 7df4fa2be7..d758e7ba1b 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -127,14 +127,18 @@ func fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks return ctx.Err() } + var empty int for _, blk := range blks { if blk.IsEmpty() { - // NOTE: This check verifies that Bitswap did it job correctly and gave us everything - // requested. If there is still an empty block somewhere this suggests there is a bug - // on the intersection of Bitswap and Fetch function. - return fmt.Errorf("got empty block from Bitswap: %s", blk.CID()) + empty++ } } + if empty > 0 { + // NOTE: This check verifies that Bitswap did it job correctly and gave us everything + // requested. If there is still an empty block somewhere this suggests there is a bug + // on the intersection of Bitswap and Fetch function. + return fmt.Errorf("got %d empty of %d blocks from Bitswap", empty, len(blks)) + } return nil } From 5ad3d762e97535c7ea2291df9c88bea5e5c3f376 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Tue, 25 Jun 2024 17:15:19 +0200 Subject: [PATCH 38/40] introduce test block and avoid testing Fetch functions over real blocks --- share/shwap/p2p/bitswap/block_fetch_test.go | 79 ++++++------ share/shwap/p2p/bitswap/block_registry.go | 10 +- share/shwap/p2p/bitswap/block_store.go | 27 ++-- share/shwap/p2p/bitswap/block_test.go | 116 ++++++++++++++++++ share/shwap/p2p/bitswap/cid.go | 2 +- share/shwap/p2p/bitswap/row_block_test.go | 4 +- .../bitswap/row_namespace_data_block_test.go | 4 +- share/shwap/p2p/bitswap/sample_block_test.go | 4 +- 8 files changed, 188 insertions(+), 58 deletions(-) create mode 100644 share/shwap/p2p/bitswap/block_test.go diff --git a/share/shwap/p2p/bitswap/block_fetch_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go index 53b6ae1033..9b4c4f59cd 100644 --- a/share/shwap/p2p/bitswap/block_fetch_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -25,30 +25,29 @@ import ( "github.com/celestiaorg/rsmt2d" - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/eds/edstest" eds "github.com/celestiaorg/celestia-node/share/new_eds" ) -func TestFetchOptions(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) +func TestFetch_Options(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() - eds := edstest.RandEDS(t, 4) - root, err := share.NewRoot(eds) - require.NoError(t, err) - exchange := newExchange(ctx, t, eds) - - blks := make([]Block, eds.Width()) - for i := range blks { - blk, err := NewEmptyRowBlock(1, i, root) // create the same Block ID - require.NoError(t, err) - blks[i] = blk - } + const items = 128 + bstore, cids := testBlockstore(ctx, t, items) t.Run("WithBlockstore", func(t *testing.T) { + exchange := newExchange(ctx, t, bstore) + + blks := make([]Block, 0, cids.Len()) + _ = cids.ForEach(func(c cid.Cid) error { + blk, err := newEmptyTestBlock(c) + require.NoError(t, err) + blks = append(blks, blk) + return nil + }) + bstore := blockstore.NewBlockstore(ds.NewMapDatastore()) - err := Fetch(ctx, exchange, root, blks, WithStore(bstore)) + err := Fetch(ctx, exchange, nil, blks, WithStore(bstore)) require.NoError(t, err) for _, blk := range blks { @@ -59,31 +58,41 @@ func TestFetchOptions(t *testing.T) { }) t.Run("WithFetcher", func(t *testing.T) { + exchange := newExchange(ctx, t, bstore) + + blks := make([]Block, 0, cids.Len()) + _ = cids.ForEach(func(c cid.Cid) error { + blk, err := newEmptyTestBlock(c) + require.NoError(t, err) + blks = append(blks, blk) + return nil + }) + session := exchange.NewSession(ctx) fetcher := &testFetcher{Embedded: session} - err := Fetch(ctx, exchange, root, blks, WithFetcher(fetcher)) + err := Fetch(ctx, exchange, nil, blks, WithFetcher(fetcher)) require.NoError(t, err) require.Equal(t, len(blks), fetcher.Fetched) }) } -func TestFetchDuplicates(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) +func TestFetch_Duplicates(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - eds := edstest.RandEDS(t, 4) - root, err := share.NewRoot(eds) - require.NoError(t, err) - exchange := newExchange(ctx, t, eds) + const items = 128 + bstore, cids := testBlockstore(ctx, t, items) + exchange := newExchange(ctx, t, bstore) var wg sync.WaitGroup - for i := range 100 { - blks := make([]Block, eds.Width()) - for i := range blks { - blk, err := NewEmptyRowBlock(1, i, root) // create the same Block ID + for i := range items { + blks := make([]Block, 0, cids.Len()) + _ = cids.ForEach(func(c cid.Cid) error { + blk, err := newEmptyTestBlock(c) require.NoError(t, err) - blks[i] = blk - } + blks = append(blks, blk) + return nil + }) wg.Add(1) go func(i int) { @@ -91,11 +100,8 @@ func TestFetchDuplicates(t *testing.T) { // this sleep ensures fetches aren't started simultaneously, allowing to check for edge-cases time.Sleep(time.Millisecond * time.Duration(rint)) - err := Fetch(ctx, exchange, root, blks) + err := Fetch(ctx, exchange, nil, blks) assert.NoError(t, err) - for _, blk := range blks { - assert.False(t, blk.IsEmpty()) - } wg.Done() }(i) } @@ -110,13 +116,16 @@ func TestFetchDuplicates(t *testing.T) { require.Zero(t, entries) } -func newExchange(ctx context.Context, t *testing.T, rsmt2dEds *rsmt2d.ExtendedDataSquare) exchange.SessionExchange { +func newExchangeOverEDS(ctx context.Context, t *testing.T, rsmt2d *rsmt2d.ExtendedDataSquare) exchange.SessionExchange { bstore := &Blockstore{ Getter: testAccessorGetter{ - Accessor: eds.Rsmt2D{ExtendedDataSquare: rsmt2dEds}, + Accessor: eds.Rsmt2D{ExtendedDataSquare: rsmt2d}, }, } + return newExchange(ctx, t, bstore) +} +func newExchange(ctx context.Context, t *testing.T, bstore blockstore.Blockstore) exchange.SessionExchange { net, err := mocknet.FullMeshLinked(3) require.NoError(t, err) diff --git a/share/shwap/p2p/bitswap/block_registry.go b/share/shwap/p2p/bitswap/block_registry.go index b547e37859..495167aea7 100644 --- a/share/shwap/p2p/bitswap/block_registry.go +++ b/share/shwap/p2p/bitswap/block_registry.go @@ -9,12 +9,12 @@ import ( ) // registerBlock registers the new Block type and multihash for it. -func registerBlock(mhcode, codec uint64, size int, bldrFn func(cid.Cid) (Block, error)) { +func registerBlock(mhcode, codec uint64, idSize int, bldrFn func(cid.Cid) (Block, error)) { mh.Register(mhcode, func() hash.Hash { - return &hasher{IDSize: size} + return &hasher{IDSize: idSize} }) specRegistry[mhcode] = blockSpec{ - size: size, + idSize: idSize, codec: codec, builder: bldrFn, } @@ -22,13 +22,13 @@ func registerBlock(mhcode, codec uint64, size int, bldrFn func(cid.Cid) (Block, // blockSpec holds constant metadata about particular Block types. type blockSpec struct { - size int + idSize int codec uint64 builder func(cid.Cid) (Block, error) } func (spec *blockSpec) String() string { - return fmt.Sprintf("BlockSpec{size: %d, codec: %d}", spec.size, spec.codec) + return fmt.Sprintf("BlockSpec{IDSize: %d, Codec: %d}", spec.idSize, spec.codec) } var specRegistry = make(map[uint64]blockSpec) diff --git a/share/shwap/p2p/bitswap/block_store.go b/share/shwap/p2p/bitswap/block_store.go index 700d8798c7..ffc6b0ede7 100644 --- a/share/shwap/p2p/bitswap/block_store.go +++ b/share/shwap/p2p/bitswap/block_store.go @@ -42,17 +42,7 @@ func (b *Blockstore) getBlock(ctx context.Context, cid cid.Cid) (blocks.Block, e return nil, fmt.Errorf("failed to populate Shwap Block on height %v for %s: %w", blk.Height(), spec.String(), err) } - protoData, err := marshalProto(blk) - if err != nil { - return nil, fmt.Errorf("failed to wrap Block with proto: %w", err) - } - - bitswapBlk, err := blocks.NewBlockWithCid(protoData, cid) - if err != nil { - return nil, fmt.Errorf("assembling Bitswap block: %w", err) - } - - return bitswapBlk, nil + return convertBitswap(blk) } func (b *Blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { @@ -100,3 +90,18 @@ func (b *Blockstore) DeleteBlock(context.Context, cid.Cid) error { func (b *Blockstore) AllKeysChan(context.Context) (<-chan cid.Cid, error) { panic("not implemented") } func (b *Blockstore) HashOnRead(bool) { panic("not implemented") } + +// convertBitswap converts and marshals Block to Bitswap Block. +func convertBitswap(blk Block) (blocks.Block, error) { + protoData, err := marshalProto(blk) + if err != nil { + return nil, fmt.Errorf("failed to wrap Block with proto: %w", err) + } + + bitswapBlk, err := blocks.NewBlockWithCid(protoData, blk.CID()) + if err != nil { + return nil, fmt.Errorf("assembling Bitswap block: %w", err) + } + + return bitswapBlk, nil +} diff --git a/share/shwap/p2p/bitswap/block_test.go b/share/shwap/p2p/bitswap/block_test.go new file mode 100644 index 0000000000..8fc046b56c --- /dev/null +++ b/share/shwap/p2p/bitswap/block_test.go @@ -0,0 +1,116 @@ +package bitswap + +import ( + "context" + crand "crypto/rand" + "encoding/binary" + "testing" + "time" + + "github.com/ipfs/boxo/blockstore" + "github.com/ipfs/go-cid" + ds "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/share" + eds "github.com/celestiaorg/celestia-node/share/new_eds" +) + +const ( + testCodec = 0x9999 + testMultihashCode = 0x9999 + testIDSize = 2 +) + +func init() { + registerBlock( + testMultihashCode, + testCodec, + testIDSize, + func(cid cid.Cid) (Block, error) { + return newEmptyTestBlock(cid) + }, + ) +} + +func testBlockstore(ctx context.Context, t *testing.T, items int) (blockstore.Blockstore, *cid.Set) { + bstore := blockstore.NewBlockstore(dssync.MutexWrap(ds.NewMapDatastore())) + + cids := cid.NewSet() + for i := range items { + blk := newTestBlock(i) + bitswapBlk, err := convertBitswap(blk) + require.NoError(t, err) + err = bstore.Put(ctx, bitswapBlk) + require.NoError(t, err) + cids.Add(blk.CID()) + } + return bstore, cids +} + +type testID uint16 + +func (t testID) MarshalBinary() (data []byte, err error) { + data = binary.BigEndian.AppendUint16(data, uint16(t)) + return data, nil +} + +func (t *testID) UnmarshalBinary(data []byte) error { + *t = testID(binary.BigEndian.Uint16(data)) + return nil +} + +type testBlock struct { + id testID + data []byte +} + +func newTestBlock(id int) *testBlock { + bytes := make([]byte, 256) + _, _ = crand.Read(bytes) + return &testBlock{id: testID(id), data: bytes} +} + +func newEmptyTestBlock(cid cid.Cid) (*testBlock, error) { + idData, err := extractCID(cid) + if err != nil { + return nil, err + } + + var id testID + err = id.UnmarshalBinary(idData) + if err != nil { + return nil, err + } + + return &testBlock{id: id}, nil +} + +func (t *testBlock) CID() cid.Cid { + return encodeCID(t.id, testMultihashCode, testCodec) +} + +func (t *testBlock) Height() uint64 { + return 1 +} + +func (t *testBlock) IsEmpty() bool { + return t.data == nil +} + +func (t *testBlock) Populate(context.Context, eds.Accessor) error { + return nil // noop +} + +func (t *testBlock) Marshal() ([]byte, error) { + return t.data, nil +} + +func (t *testBlock) UnmarshalFn(*share.Root) UnmarshalFn { + return func(bytes []byte) error { + t.data = bytes + time.Sleep(time.Millisecond * 1) + return nil + } +} diff --git a/share/shwap/p2p/bitswap/cid.go b/share/shwap/p2p/bitswap/cid.go index ea65715d2e..8e93fed548 100644 --- a/share/shwap/p2p/bitswap/cid.go +++ b/share/shwap/p2p/bitswap/cid.go @@ -45,7 +45,7 @@ func validateCID(cid cid.Cid) error { return fmt.Errorf("invalid CID codec %d", prefix.Codec) } - if prefix.MhLength != spec.size { + if prefix.MhLength != spec.idSize { return fmt.Errorf("invalid multihash length %d", prefix.MhLength) } diff --git a/share/shwap/p2p/bitswap/row_block_test.go b/share/shwap/p2p/bitswap/row_block_test.go index 85d71717a5..36902b0e76 100644 --- a/share/shwap/p2p/bitswap/row_block_test.go +++ b/share/shwap/p2p/bitswap/row_block_test.go @@ -11,14 +11,14 @@ import ( "github.com/celestiaorg/celestia-node/share/eds/edstest" ) -func TestRowRoundtrip_GetContainers(t *testing.T) { +func TestRow_FetchRoundtrip(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() eds := edstest.RandEDS(t, 4) root, err := share.NewRoot(eds) require.NoError(t, err) - exchange := newExchange(ctx, t, eds) + exchange := newExchangeOverEDS(ctx, t, eds) blks := make([]Block, eds.Width()) for i := range blks { diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block_test.go b/share/shwap/p2p/bitswap/row_namespace_data_block_test.go index a4a878cf7b..391ee7a825 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block_test.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block_test.go @@ -12,13 +12,13 @@ import ( "github.com/celestiaorg/celestia-node/share/sharetest" ) -func TestRowNamespaceDataRoundtrip_GetContainers(t *testing.T) { +func TestRowNamespaceData_FetchRoundtrip(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() namespace := sharetest.RandV0Namespace() eds, root := edstest.RandEDSWithNamespace(t, namespace, 64, 16) - exchange := newExchange(ctx, t, eds) + exchange := newExchangeOverEDS(ctx, t, eds) rowIdxs := share.RowsWithNamespace(root, namespace) blks := make([]Block, len(rowIdxs)) diff --git a/share/shwap/p2p/bitswap/sample_block_test.go b/share/shwap/p2p/bitswap/sample_block_test.go index ccea1eb4cc..0733acebdf 100644 --- a/share/shwap/p2p/bitswap/sample_block_test.go +++ b/share/shwap/p2p/bitswap/sample_block_test.go @@ -11,14 +11,14 @@ import ( "github.com/celestiaorg/celestia-node/share/eds/edstest" ) -func TestSampleRoundtrip_GetContainers(t *testing.T) { +func TestSample_FetchRoundtrip(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() eds := edstest.RandEDS(t, 32) root, err := share.NewRoot(eds) require.NoError(t, err) - exchange := newExchange(ctx, t, eds) + exchange := newExchangeOverEDS(ctx, t, eds) width := int(eds.Width()) blks := make([]Block, 0, width*width) From 4842fcbe199c38f884013af0a732d456fd2b6945 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Wed, 26 Jun 2024 14:10:55 +0200 Subject: [PATCH 39/40] comment improve and simplyf test construction --- share/shwap/p2p/bitswap/block_fetch.go | 5 +++-- share/shwap/p2p/bitswap/block_fetch_test.go | 12 +++--------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index d758e7ba1b..613ef8df2a 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -100,8 +100,9 @@ func fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks blk, ok := duplicates[bitswapBlk.Cid()] if ok { - // uncommon duplicate case: concurrent fetching of the same block, - // so we have to unmarshal it ourselves instead of the hasher, + // uncommon duplicate case: concurrent fetching of the same block. + // The block hasn't been invoked inside hasher verification, + // so we have to unmarshal it ourselves. unmarshalFn := blk.UnmarshalFn(root) err := unmarshal(unmarshalFn, bitswapBlk.RawData()) if err != nil { diff --git a/share/shwap/p2p/bitswap/block_fetch_test.go b/share/shwap/p2p/bitswap/block_fetch_test.go index 9b4c4f59cd..1c8aa367e1 100644 --- a/share/shwap/p2p/bitswap/block_fetch_test.go +++ b/share/shwap/p2p/bitswap/block_fetch_test.go @@ -12,12 +12,10 @@ import ( "github.com/ipfs/boxo/bitswap/server" "github.com/ipfs/boxo/blockstore" "github.com/ipfs/boxo/exchange" - "github.com/ipfs/boxo/routing/offline" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" - dssync "github.com/ipfs/go-datastore/sync" - record "github.com/libp2p/go-libp2p-record" + routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" "github.com/libp2p/go-libp2p/core/host" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" @@ -140,9 +138,7 @@ func newExchange(ctx context.Context, t *testing.T, bstore blockstore.Blockstore } func newServer(ctx context.Context, host host.Host, store blockstore.Blockstore) { - dstore := dssync.MutexWrap(ds.NewMapDatastore()) - routing := offline.NewOfflineRouter(dstore, record.NamespacedValidator{}) - net := network.NewFromIpfsHost(host, routing) + net := network.NewFromIpfsHost(host, routinghelpers.Null{}) server := server.New( ctx, net, @@ -156,9 +152,7 @@ func newServer(ctx context.Context, host host.Host, store blockstore.Blockstore) } func newClient(ctx context.Context, host host.Host, store blockstore.Blockstore) *client.Client { - dstore := dssync.MutexWrap(ds.NewMapDatastore()) - routing := offline.NewOfflineRouter(dstore, record.NamespacedValidator{}) - net := network.NewFromIpfsHost(host, routing) + net := network.NewFromIpfsHost(host, routinghelpers.Null{}) client := client.New( ctx, net, From f88f5ed79e76773e15228ea60ebe786c0b5f747f Mon Sep 17 00:00:00 2001 From: Wondertan Date: Wed, 26 Jun 2024 14:18:14 +0200 Subject: [PATCH 40/40] simplify and remove IsEmpty --- share/shwap/p2p/bitswap/block.go | 2 -- share/shwap/p2p/bitswap/block_fetch.go | 18 +----------------- share/shwap/p2p/bitswap/block_test.go | 4 ---- share/shwap/p2p/bitswap/row_block.go | 9 +++------ .../p2p/bitswap/row_namespace_data_block.go | 9 +++------ share/shwap/p2p/bitswap/sample_block.go | 9 +++------ 6 files changed, 10 insertions(+), 41 deletions(-) diff --git a/share/shwap/p2p/bitswap/block.go b/share/shwap/p2p/bitswap/block.go index 6cb3c22a35..6825fc43ce 100644 --- a/share/shwap/p2p/bitswap/block.go +++ b/share/shwap/p2p/bitswap/block.go @@ -22,8 +22,6 @@ type Block interface { // Height reports the Height of the Shwap container behind the Block. Height() uint64 - // IsEmpty reports whether the Block holds respective Shwap container. - IsEmpty() bool // Populate fills up the Block with the Shwap container getting it out of the EDS // Accessor. Populate(context.Context, eds.Accessor) error diff --git a/share/shwap/p2p/bitswap/block_fetch.go b/share/shwap/p2p/bitswap/block_fetch.go index 613ef8df2a..4a66eb72ef 100644 --- a/share/shwap/p2p/bitswap/block_fetch.go +++ b/share/shwap/p2p/bitswap/block_fetch.go @@ -124,24 +124,8 @@ func fetch(ctx context.Context, exchg exchange.Interface, root *share.Root, blks log.Error("failed to store the new Bitswap block: %s", err) } } - if ctx.Err() != nil { - return ctx.Err() - } - - var empty int - for _, blk := range blks { - if blk.IsEmpty() { - empty++ - } - } - if empty > 0 { - // NOTE: This check verifies that Bitswap did it job correctly and gave us everything - // requested. If there is still an empty block somewhere this suggests there is a bug - // on the intersection of Bitswap and Fetch function. - return fmt.Errorf("got %d empty of %d blocks from Bitswap", empty, len(blks)) - } - return nil + return ctx.Err() } // unmarshal unmarshalls the Shwap Container data into a Block with the given UnmarshalFn diff --git a/share/shwap/p2p/bitswap/block_test.go b/share/shwap/p2p/bitswap/block_test.go index 8fc046b56c..2f01229dfc 100644 --- a/share/shwap/p2p/bitswap/block_test.go +++ b/share/shwap/p2p/bitswap/block_test.go @@ -95,10 +95,6 @@ func (t *testBlock) Height() uint64 { return 1 } -func (t *testBlock) IsEmpty() bool { - return t.data == nil -} - func (t *testBlock) Populate(context.Context, eds.Accessor) error { return nil // noop } diff --git a/share/shwap/p2p/bitswap/row_block.go b/share/shwap/p2p/bitswap/row_block.go index 97513fd10a..927b7f320b 100644 --- a/share/shwap/p2p/bitswap/row_block.go +++ b/share/shwap/p2p/bitswap/row_block.go @@ -73,7 +73,7 @@ func (rb *RowBlock) Height() uint64 { } func (rb *RowBlock) Marshal() ([]byte, error) { - if rb.IsEmpty() { + if rb.Container.IsEmpty() { return nil, fmt.Errorf("cannot marshal empty RowBlock") } @@ -96,15 +96,12 @@ func (rb *RowBlock) Populate(ctx context.Context, eds eds.Accessor) error { return nil } -func (rb *RowBlock) IsEmpty() bool { - return rb.Container.IsEmpty() -} - func (rb *RowBlock) UnmarshalFn(root *share.Root) UnmarshalFn { return func(data []byte) error { - if !rb.IsEmpty() { + if !rb.Container.IsEmpty() { return nil } + var row shwappb.Row if err := row.Unmarshal(data); err != nil { return fmt.Errorf("unmarshaling Row: %w", err) diff --git a/share/shwap/p2p/bitswap/row_namespace_data_block.go b/share/shwap/p2p/bitswap/row_namespace_data_block.go index 8c3f15ab64..6b7f905fe2 100644 --- a/share/shwap/p2p/bitswap/row_namespace_data_block.go +++ b/share/shwap/p2p/bitswap/row_namespace_data_block.go @@ -77,7 +77,7 @@ func (rndb *RowNamespaceDataBlock) Height() uint64 { } func (rndb *RowNamespaceDataBlock) Marshal() ([]byte, error) { - if rndb.IsEmpty() { + if rndb.Container.IsEmpty() { return nil, fmt.Errorf("cannot marshal empty RowNamespaceDataBlock") } @@ -100,15 +100,12 @@ func (rndb *RowNamespaceDataBlock) Populate(ctx context.Context, eds eds.Accesso return nil } -func (rndb *RowNamespaceDataBlock) IsEmpty() bool { - return rndb.Container.IsEmpty() -} - func (rndb *RowNamespaceDataBlock) UnmarshalFn(root *share.Root) UnmarshalFn { return func(data []byte) error { - if !rndb.IsEmpty() { + if !rndb.Container.IsEmpty() { return nil } + var rnd shwappb.RowNamespaceData if err := rnd.Unmarshal(data); err != nil { return fmt.Errorf("unmarshaling RowNamespaceData: %w", err) diff --git a/share/shwap/p2p/bitswap/sample_block.go b/share/shwap/p2p/bitswap/sample_block.go index 3c6a341cad..625ca2bf08 100644 --- a/share/shwap/p2p/bitswap/sample_block.go +++ b/share/shwap/p2p/bitswap/sample_block.go @@ -72,7 +72,7 @@ func (sb *SampleBlock) Height() uint64 { } func (sb *SampleBlock) Marshal() ([]byte, error) { - if sb.IsEmpty() { + if sb.Container.IsEmpty() { return nil, fmt.Errorf("cannot marshal empty SampleBlock") } @@ -95,15 +95,12 @@ func (sb *SampleBlock) Populate(ctx context.Context, eds eds.Accessor) error { return nil } -func (sb *SampleBlock) IsEmpty() bool { - return sb.Container.IsEmpty() -} - func (sb *SampleBlock) UnmarshalFn(root *share.Root) UnmarshalFn { return func(data []byte) error { - if !sb.IsEmpty() { + if !sb.Container.IsEmpty() { return nil } + var sample shwappb.Sample if err := sample.Unmarshal(data); err != nil { return fmt.Errorf("unmarshaling Sample: %w", err)