Repository: celestiaorg/celestia-node
Pull request #1298 (2022-10-28 16:56:56)

feat(header/p2p): implement gcing for peerTracker
Resolves https://github.com/celestiaorg/celestia-node/issues/1411, https://github.com/celestiaorg/celestia-node/issues/1317

codecov-commenter: # [Codecov](https://codecov.io/gh/celestiaorg/celestia-node/pull/1298?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=celestiaorg) Report
> Merging [#1298](https://codecov.io/gh/celestiaorg/celestia-node/pull/1298?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=celestiaorg) (a89d15b) into [main](https://codecov.io/gh/celestiaorg/celestia-node/commit/da4f54bca1bfef86f53880ced569d37ffb4b8b84?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=celestiaorg) (da4f54b) will **increase** coverage by `0.11%`.
> The diff coverage is `70.66%`.

> :exclamation: Current head a89d15b differs from pull request most recent head c31f1eb. Consider uploading reports for the commit c31f1eb to get more accurate results

```diff
@@            Coverage Diff             @@
##             main    #1298      +/-   ##
==========================================
+ Coverage   56.02%   56.13%   +0.11%     
==========================================
  Files         188      188              
  Lines       11551    11616      +65     
==========================================
+ Hits         6471     6521      +50     
- Misses       4443     4454      +11     
- Partials      637      641       +4     
```


| [Impacted Files](https://codecov.io/gh/celestiaorg/celestia-node/pull/1298?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=celestiaorg) | Coverage Δ | |
|---|---|---|
| [header/p2p/peer\_stats.go](https://codecov.io/gh/celestiaorg/celestia-node/pull/1298/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=celestiaorg#diff-aGVhZGVyL3AycC9wZWVyX3N0YXRzLmdv) | `93.54% <ø> (ø)` | |
| [header/p2p/options.go](https://codecov.io/gh/celestiaorg/celestia-node/pull/1298/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=celestiaorg#diff-aGVhZGVyL3AycC9vcHRpb25zLmdv) | `65.59% <46.15%> (-4.91%)` | :arrow_down: |
| [header/p2p/peer\_tracker.go](https://codecov.io/gh/celestiaorg/celestia-node/pull/1298/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=celestiaorg#diff-aGVhZGVyL3AycC9wZWVyX3RyYWNrZXIuZ28=) | `69.38% <96.87%> (+12.03%)` | :arrow_up: |
| [header/p2p/exchange.go](https://codecov.io/gh/celestiaorg/celestia-node/pull/1298/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=celestiaorg#diff-aGVhZGVyL3AycC9leGNoYW5nZS5nbw==) | `73.02% <100.00%> (ø)` | |
| [nodebuilder/header/module.go](https://codecov.io/gh/celestiaorg/celestia-node/pull/1298/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=celestiaorg#diff-bm9kZWJ1aWxkZXIvaGVhZGVyL21vZHVsZS5nbw==) | `94.25% <100.00%> (+0.20%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=celestiaorg). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=celestiaorg)


Patch:
diff --git a/Makefile b/Makefile
index e148abf6..b3755e68 100644
--- a/Makefile
+++ b/Makefile
@@ -151,6 +151,6 @@ pb-gen:
 ## openrpc-gen: Generate OpenRPC spec for Celestia-Node's RPC api
 openrpc-gen:
 	@echo "--> Generating OpenRPC spec"
-	@go run ./cmd/docgen fraud header state share das
+	@go run ./cmd/docgen fraud header state share das p2p
 .PHONY: openrpc-gen
 
diff --git a/api/gateway/availability.go b/api/gateway/availability.go
index 37d61ee4..e5e53e0d 100644
--- a/api/gateway/availability.go
+++ b/api/gateway/availability.go
@@ -35,7 +35,7 @@ func (h *Handler) handleHeightAvailabilityRequest(w http.ResponseWriter, r *http
 
 	availResp := &AvailabilityResponse{
 		Probability: strconv.FormatFloat(
-			h.share.ProbabilityOfAvailability(), 'g', -1, 64),
+			h.share.ProbabilityOfAvailability(r.Context()), 'g', -1, 64),
 	}
 
 	err = h.share.SharesAvailable(r.Context(), header.DAH)
diff --git a/api/gateway/middleware.go b/api/gateway/middleware.go
index f71d3c3d..498b9c5d 100644
--- a/api/gateway/middleware.go
+++ b/api/gateway/middleware.go
@@ -33,7 +33,7 @@ func checkPostDisabled(state state.Module) mux.MiddlewareFunc {
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			// check if state service was halted and deny the transaction
-			if r.Method == http.MethodPost && state.IsStopped() {
+			if r.Method == http.MethodPost && state.IsStopped(r.Context()) {
 				writeError(w, http.StatusMethodNotAllowed, r.URL.Path, errors.New("not possible to submit data"))
 				return
 			}
diff --git a/api/rpc/client/client.go b/api/rpc/client/client.go
index c22d1bec..2e8c74d2 100644
--- a/api/rpc/client/client.go
+++ b/api/rpc/client/client.go
@@ -62,12 +62,12 @@ func NewClient(ctx context.Context, addr string) (*Client, error) {
 
 	// TODO: this duplication of strings many times across the codebase can be avoided with issue #1176
 	var modules = map[string]interface{}{
-		"share":  &client.Share,
-		"state":  &client.State,
-		"header": &client.Header,
-		"fraud":  &client.Fraud,
-		"das":    &client.DAS,
-		"p2p":    &client.P2P,
+		"share":  &client.Share.Internal,
+		"state":  &client.State.Internal,
+		"header": &client.Header.Internal,
+		"fraud":  &client.Fraud.Internal,
+		"das":    &client.DAS.Internal,
+		"p2p":    &client.P2P.Internal,
 	}
 	for name, module := range modules {
 		closer, err := jsonrpc.NewClient(ctx, addr, name, module, nil)
diff --git a/api/rpc/server.go b/api/rpc/server.go
index 85d59cca..68489416 100644
--- a/api/rpc/server.go
+++ b/api/rpc/server.go
@@ -4,15 +4,22 @@ import (
 	"context"
 	"net"
 	"net/http"
+	"reflect"
 	"sync/atomic"
 	"time"
 
 	"github.com/filecoin-project/go-jsonrpc"
+	"github.com/filecoin-project/go-jsonrpc/auth"
 	logging "github.com/ipfs/go-log/v2"
 )
 
 var log = logging.Logger("rpc")
 
+var (
+	AllPerms     = []auth.Permission{"public", "read", "write", "admin"}
+	DefaultPerms = []auth.Permission{"public"}
+)
+
 type Server struct {
 	srv      *http.Server
 	rpc      *jsonrpc.RPCServer
@@ -23,11 +30,19 @@ type Server struct {
 
 func NewServer(address, port string) *Server {
 	rpc := jsonrpc.NewServer()
+	authHandler := &auth.Handler{
+		Verify: func(ctx context.Context, token string) ([]auth.Permission, error) {
+			// TODO(distractedm1nd/renaynay): implement auth
+			log.Warn("auth not implemented, token: ", token)
+			return DefaultPerms, nil
+		},
+		Next: rpc.ServeHTTP,
+	}
 	return &Server{
 		rpc: rpc,
 		srv: &http.Server{
 			Addr:    address + ":" + port,
-			Handler: rpc,
+			Handler: authHandler,
 			// the amount of time allowed to read request headers. set to the default 2 seconds
 			ReadHeaderTimeout: 2 * time.Second,
 		},
@@ -40,6 +55,17 @@ func (s *Server) RegisterService(namespace string, service interface{}) {
 	s.rpc.Register(namespace, service)
 }
 
+// RegisterAuthedService registers a service onto the RPC server. All methods on the service will
+// then be exposed over the RPC.
+func (s *Server) RegisterAuthedService(namespace string, service interface{}, out interface{}) {
+	auth.PermissionedProxy(AllPerms, DefaultPerms, service, getInternalStruct(out))
+	s.RegisterService(namespace, out)
+}
+
+func getInternalStruct(api interface{}) interface{} {
+	return reflect.ValueOf(api).Elem().FieldByName("Internal").Addr().Interface()
+}
+
 // Start starts the RPC Server.
 func (s *Server) Start(context.Context) error {
 	couldStart := s.started.CompareAndSwap(false, true)
diff --git a/cmd/cel-key/node_types.go b/cmd/cel-key/node_types.go
index 0a36b04c..9c091ada 100644
--- a/cmd/cel-key/node_types.go
+++ b/cmd/cel-key/node_types.go
@@ -14,8 +14,8 @@ import (
 )
 
 var (
-	nodeDirKey     = "node.type"
-	nodeNetworkKey = "node.network"
+	nodeDirKey = "node.type"
+	networkKey = "p2p.network"
 )
 
 func DirectoryFlags() *flag.FlagSet {
@@ -28,7 +28,7 @@ func DirectoryFlags() *flag.FlagSet {
 		"Sets key utility to use the node type's directory (e.g. "+
 			"~/.celestia-light-"+strings.ToLower(defaultNetwork)+" if --node.type light is passed).")
 	flags.String(
-		nodeNetworkKey,
+		networkKey,
 		defaultNetwork,
 		"Sets key utility to use the node network's directory (e.g. "+
 			"~/.celestia-light-mynetwork if --node.network MyNetwork is passed).")
@@ -42,7 +42,7 @@ func ParseDirectoryFlags(cmd *cobra.Command) error {
 		return errors.New("no node type provided")
 	}
 
-	network := cmd.Flag(nodeNetworkKey).Value.String()
+	network := cmd.Flag(networkKey).Value.String()
 	switch nodeType {
 	case "bridge", "full", "light":
 		keyPath := fmt.Sprintf("~/.celestia-%s-%s/keys", nodeType, strings.ToLower(network))
diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go
index a4ddd992..8aeb632d 100644
--- a/cmd/celestia/bridge.go
+++ b/cmd/celestia/bridge.go
@@ -43,48 +43,6 @@ var bridgeCmd = &cobra.Command{
 	Args:  cobra.NoArgs,
 	Short: "Manage your Bridge node",
 	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
-		var (
-			ctx = cmd.Context()
-			err error
-		)
-
-		ctx = cmdnode.WithNodeType(ctx, node.Bridge)
-
-		parsedNetwork, err := p2p.ParseNetwork(cmd)
-		if err != nil {
-			return err
-		}
-		ctx = cmdnode.WithNetwork(ctx, parsedNetwork)
-
-		ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx))
-		if err != nil {
-			return err
-		}
-
-		cfg := cmdnode.NodeConfig(ctx)
-
-		err = p2p.ParseFlags(cmd, &cfg.P2P)
-		if err != nil {
-			return err
-		}
-
-		err = core.ParseFlags(cmd, &cfg.Core)
-		if err != nil {
-			return err
-		}
-
-		ctx, err = cmdnode.ParseMiscFlags(ctx, cmd)
-		if err != nil {
-			return err
-		}
-
-		rpc.ParseFlags(cmd, &cfg.RPC)
-		gateway.ParseFlags(cmd, &cfg.Gateway)
-		state.ParseFlags(cmd, &cfg.State)
-
-		// set config
-		ctx = cmdnode.WithNodeConfig(ctx, &cfg)
-		cmd.SetContext(ctx)
-		return nil
+		return persistentPreRunEnv(cmd, node.Bridge, args)
 	},
 }
diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go
index 25454521..0cd7fd19 100644
--- a/cmd/celestia/full.go
+++ b/cmd/celestia/full.go
@@ -1,4 +1,3 @@
-//nolint:dupl
 package main
 
 import (
@@ -51,53 +50,6 @@ var fullCmd = &cobra.Command{
 	Args:  cobra.NoArgs,
 	Short: "Manage your Full node",
 	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
-		var (
-			ctx = cmd.Context()
-			err error
-		)
-
-		ctx = cmdnode.WithNodeType(ctx, node.Full)
-
-		parsedNetwork, err := p2p.ParseNetwork(cmd)
-		if err != nil {
-			return err
-		}
-		ctx = cmdnode.WithNetwork(ctx, parsedNetwork)
-
-		ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx))
-		if err != nil {
-			return err
-		}
-
-		cfg := cmdnode.NodeConfig(ctx)
-
-		err = p2p.ParseFlags(cmd, &cfg.P2P)
-		if err != nil {
-			return err
-		}
-
-		err = core.ParseFlags(cmd, &cfg.Core)
-		if err != nil {
-			return err
-		}
-
-		err = header.ParseFlags(cmd, &cfg.Header)
-		if err != nil {
-			return err
-		}
-
-		ctx, err = cmdnode.ParseMiscFlags(ctx, cmd)
-		if err != nil {
-			return err
-		}
-
-		rpc.ParseFlags(cmd, &cfg.RPC)
-		gateway.ParseFlags(cmd, &cfg.Gateway)
-		state.ParseFlags(cmd, &cfg.State)
-
-		// set config
-		ctx = cmdnode.WithNodeConfig(ctx, &cfg)
-		cmd.SetContext(ctx)
-		return nil
+		return persistentPreRunEnv(cmd, node.Full, args)
 	},
 }
diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go
index d035f0ae..a56d4b30 100644
--- a/cmd/celestia/light.go
+++ b/cmd/celestia/light.go
@@ -1,4 +1,3 @@
-//nolint:dupl
 package main
 
 import (
@@ -51,54 +50,6 @@ var lightCmd = &cobra.Command{
 	Args:  cobra.NoArgs,
 	Short: "Manage your Light node",
 	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
-		var (
-			ctx = cmd.Context()
-			err error
-		)
-
-		ctx = cmdnode.WithNodeType(ctx, node.Light)
-
-		parsedNetwork, err := p2p.ParseNetwork(cmd)
-		if err != nil {
-			return err
-		}
-		ctx = cmdnode.WithNetwork(ctx, parsedNetwork)
-
-		// loads existing config into the environment
-		ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx))
-		if err != nil {
-			return err
-		}
-
-		cfg := cmdnode.NodeConfig(ctx)
-
-		err = p2p.ParseFlags(cmd, &cfg.P2P)
-		if err != nil {
-			return err
-		}
-
-		err = core.ParseFlags(cmd, &cfg.Core)
-		if err != nil {
-			return err
-		}
-
-		err = header.ParseFlags(cmd, &cfg.Header)
-		if err != nil {
-			return err
-		}
-
-		ctx, err = cmdnode.ParseMiscFlags(ctx, cmd)
-		if err != nil {
-			return err
-		}
-
-		rpc.ParseFlags(cmd, &cfg.RPC)
-		gateway.ParseFlags(cmd, &cfg.Gateway)
-		state.ParseFlags(cmd, &cfg.State)
-
-		// set config
-		ctx = cmdnode.WithNodeConfig(ctx, &cfg)
-		cmd.SetContext(ctx)
-		return nil
+		return persistentPreRunEnv(cmd, node.Light, args)
 	},
 }
diff --git a/cmd/celestia/util.go b/cmd/celestia/util.go
new file mode 100644
index 00000000..9c7025fa
--- /dev/null
+++ b/cmd/celestia/util.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+	"github.com/spf13/cobra"
+
+	cmdnode "github.com/celestiaorg/celestia-node/cmd"
+	"github.com/celestiaorg/celestia-node/nodebuilder/core"
+	"github.com/celestiaorg/celestia-node/nodebuilder/gateway"
+	"github.com/celestiaorg/celestia-node/nodebuilder/header"
+	"github.com/celestiaorg/celestia-node/nodebuilder/node"
+	"github.com/celestiaorg/celestia-node/nodebuilder/p2p"
+	"github.com/celestiaorg/celestia-node/nodebuilder/rpc"
+	"github.com/celestiaorg/celestia-node/nodebuilder/state"
+)
+
+func persistentPreRunEnv(cmd *cobra.Command, nodeType node.Type, args []string) error {
+	var (
+		ctx = cmd.Context()
+		err error
+	)
+
+	ctx = cmdnode.WithNodeType(ctx, nodeType)
+
+	parsedNetwork, err := p2p.ParseNetwork(cmd)
+	if err != nil {
+		return err
+	}
+	ctx = cmdnode.WithNetwork(ctx, parsedNetwork)
+
+	// loads existing config into the environment
+	ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx))
+	if err != nil {
+		return err
+	}
+
+	cfg := cmdnode.NodeConfig(ctx)
+
+	err = p2p.ParseFlags(cmd, &cfg.P2P)
+	if err != nil {
+		return err
+	}
+
+	err = core.ParseFlags(cmd, &cfg.Core)
+	if err != nil {
+		return err
+	}
+
+	if nodeType != node.Bridge {
+		err = header.ParseFlags(cmd, &cfg.Header)
+		if err != nil {
+			return err
+		}
+	}
+
+	ctx, err = cmdnode.ParseMiscFlags(ctx, cmd)
+	if err != nil {
+		return err
+	}
+
+	rpc.ParseFlags(cmd, &cfg.RPC)
+	gateway.ParseFlags(cmd, &cfg.Gateway)
+	state.ParseFlags(cmd, &cfg.State)
+
+	// set config
+	ctx = cmdnode.WithNodeConfig(ctx, &cfg)
+	cmd.SetContext(ctx)
+	return nil
+}
diff --git a/cmd/docgen/docgen.go b/cmd/docgen/docgen.go
index 4414955c..2367ee8b 100644
--- a/cmd/docgen/docgen.go
+++ b/cmd/docgen/docgen.go
@@ -23,7 +23,7 @@ var rootCmd = &cobra.Command{
 		doc := docgen.NewOpenRPCDocument(nodeComments)
 
 		// 3. Register the client wrapper interface on the document
-		for moduleName, module := range nodebuilder.PackageToDefaultImpl {
+		for moduleName, module := range nodebuilder.PackageToAPI {
 			doc.RegisterReceiverName(moduleName, module)
 		}
 
diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md
index 56ae47a1..5d81cc47 100644
--- a/docs/adr/adr-009-public-api.md
+++ b/docs/adr/adr-009-public-api.md
@@ -141,60 +141,60 @@ SyncHead(ctx context.Context) (*header.ExtendedHeader, error)
 ```go
   type P2PModule interface {
     // Info returns address information about the host.
-    Info() peer.AddrInfo
+    Info(context.Context) peer.AddrInfo
     // Peers returns all peer IDs used across all inner stores.
-    Peers() []peer.ID
+    Peers(context.Context) []peer.ID
     // PeerInfo returns a small slice of information Peerstore has on the
     // given peer.
-    PeerInfo(id peer.ID) peer.AddrInfo
+    PeerInfo(context.Context, peer.ID) peer.AddrInfo
    
     // Connect ensures there is a connection between this host and the peer with
     // given peer.
     Connect(ctx context.Context, pi peer.AddrInfo) error
     // ClosePeer closes the connection to a given peer. 
-    ClosePeer(id peer.ID) error
+    ClosePeer(ctx context.Context, id peer.ID) error
     // Connectedness returns a state signaling connection capabilities.
-    Connectedness(id peer.ID) network.Connectedness
+    Connectedness(ctx context.Context, id peer.ID) network.Connectedness
     // NATStatus returns the current NAT status.
-    NATStatus() network.Reachability
+    NATStatus(context.Context) network.Reachability
    
     // BlockPeer adds a peer to the set of blocked peers.
-    BlockPeer(p peer.ID) error
+    BlockPeer(ctx context.Context, p peer.ID) error
     // UnblockPeer removes a peer from the set of blocked peers.
-    UnblockPeer(p peer.ID) error
+    UnblockPeer(ctx context.Context, p peer.ID) error
     // ListBlockedPeers returns a list of blocked peers.
-    ListBlockedPeers() []peer.ID
+    ListBlockedPeers(context.Context) []peer.ID
 
     // MutualAdd adds a peer to the list of peers who have a bidirectional
     // peering agreement that they are protected from being trimmed, dropped
     // or negatively scored.
-    MutualAdd(id peer.ID, tag string)
+    MutualAdd(ctx context.Context, id peer.ID, tag string)
     // MutualAdd removes a peer from the list of peers who have a bidirectional
     // peering agreement that they are protected from being trimmed, dropped
     // or negatively scored, returning a bool representing whether the given
     // peer is protected or not.
-    MutualRm(id peer.ID, tag string) bool
+    MutualRm(ctx context.Context, id peer.ID, tag string) bool
     // IsMutual returns whether the given peer is a mutual peer.
-    IsMutual(id peer.ID, tag string) bool
+    IsMutual(ctx context.Context, id peer.ID, tag string) bool
   
     // BandwidthStats returns a Stats struct with bandwidth metrics for all
     // data sent/received by the local peer, regardless of protocol or remote 
     // peer IDs.
-    BandwidthStats() Stats
+    BandwidthStats(context.Context) Stats
     // BandwidthForPeer returns a Stats struct with bandwidth metrics associated
     // with the given peer.ID. The metrics returned include all traffic sent /
     // received for the peer, regardless of protocol.
-    BandwidthForPeer(id peer.ID) Stats
+    BandwidthForPeer(ctx context.Context, id peer.ID) Stats
     // BandwidthForProtocol returns a Stats struct with bandwidth metrics 
     // associated with the given protocol.ID.
-    BandwidthForProtocol(proto protocol.ID) Stats
+    BandwidthForProtocol(ctx context.Context, proto protocol.ID) Stats
    
     // ResourceState returns the state of the resource manager.
-    ResourceState() rcmgr.ResourceManagerStat
+    ResourceState(context.Context) rcmgr.ResourceManagerStat
    
     // PubSubPeers returns the peer IDs of the peers joined on
     // the given topic.
-    PubSubPeers(topic string) ([]peer.ID, error)
+    PubSubPeers(ctx context.Context, topic string) ([]peer.ID, error)
   }
 ```
 
diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-011-blocksync-overhaul-part-1.md
similarity index 50%
rename from docs/adr/adr-11-blocksync-overhaul.md
rename to docs/adr/adr-011-blocksync-overhaul-part-1.md
index 98fea287..720f60c2 100644
--- a/docs/adr/adr-11-blocksync-overhaul.md
+++ b/docs/adr/adr-011-blocksync-overhaul-part-1.md
@@ -4,6 +4,7 @@
 
 - 23.08.22: Initial unfinished draft
 - 14.09.22: The first finished version
+- 02.12.22: Fixing missed gaps
 
 ## Authors
 
@@ -17,11 +18,10 @@
 - LN - Light Node
 - FN - Full Node
 - BN - Bridge Node
-- [EDS(Extended Data Square)](https://github.com/celestiaorg/rsmt2d/blob/master/extendeddatasquare.go#L10) - plain Block
-data omitting headers and other block metadata.
-- [NMT](https://github.com/celestiaorg/nmt) - Namespaced Merkle Tree
-- [DataRoot](https://github.com/celestiaorg/celestia-node/blob/main/service/share/share.go#L35) - alias for
-[DAHeader](https://github.com/celestiaorg/celestia-core/blob/v0.34.x-celestia/pkg/da/data_availability_header.go#L29)
+- [EDS(Extended Data Square)][eds] - plain Block data omitting headers and other block metadata
+- ODS - Original Data Square or the first quadrant of the EDS. Contains real user data and padding
+- [NMT][nmt] - Namespaced Merkle Tree
+- [DataHash][dh] - Hash commitment over [DAHeader][dah]
 
 ## Context
 
@@ -29,7 +29,7 @@ data omitting headers and other block metadata.
 
 Current block data synchronization is done over Bitswap, traversing NMT trees of rows and columns of data square quadrants.
 We know from empirical evidence that it takes more than 200 seconds(~65000 network requests) to download a 4MB block of
-256kb shares, which is unacceptable and must be much less than the block time(15/30sec).
+256kb shares, which is unacceptable and must be much less than the block time(15-30sec).
 
 The DASing, on the other hand, shows acceptable metrics for the block sizes we are aiming for initially. In the case of
 the same block, a DAS operation takes 50ms * 8(technically 9) blocking requests, which is ~400ms in an ideal scenario
@@ -53,37 +53,37 @@ in 2 days to match the following requirements:
   - Existing Bitswap logic kept as a fallback mechanism for the case of reconstruction from light nodes
 - Keeping random hash-addressed access to shares for Bitswap to work
 
-### Decision
+## Decision
 
 This ADR intends to outline design decisions for block data storage. In a nutshell, the decision is to use
-___[CAR format](https://ipld.io/specs/transport/car/carv2/)___ and ___[Dagstore](https://github.com/filecoin-project/dagstore)___
+___[CAR format][car]___ and ___[Dagstore][dagstore]___
 for ___extended block storage___ and ___custom p2p Req/Resp protocol for block data syncing___(whole block and data by
 namespace id) in the happy path. The p2p portion of the document will come in the subsequent Part II document.
 
-#### Key Design Decisions
+### Key Design Decisions
 
-- __FNs/BNs store EDSes serialized as [CAR files](https://ipld.io/specs/transport/car).__ CAR format provides an
-efficient way to store Merkle DAG data, like EDS with NMT. It packs such DAG data into a single blob which can be read
-sequentially in one read and transferred over the wire. Additionally, [CARv2](https://ipld.io/specs/transport/car/carv2/)
-introduces pluggable indexes over the blob allowing efficient random access to shares and NMT Proofs in one read
-(if the index is cached in memory).
+- __FNs/BNs store EDSes serialized as [CAR files][car].__ CAR format provides an
+  efficient way to store Merkle DAG data, like EDS with NMT. It packs such DAG data into a single blob which can be read
+  sequentially in one read and transferred over the wire. Additionally, [CARv2][carv2]
+  introduces pluggable indexes over the blob allowing efficient random access to shares and NMT Proofs in one read
+  (if the index is cached in memory).
 
 - __FNs/BNs manage a top-level index for _hash_ to _CAR block file_ mapping.__ Current DASing for LNs requires FNs/BNs
-to serve simple hash to data requests. The top-level index maps any Share/NMT Proof hash to any block CARv1 file so
-that FNs/BNs can quickly serve DA requests.
+  to serve simple hash to data requests. The top-level index maps any Share/NMT Proof hash to any block CARv1 file so
+  that FNs/BNs can quickly serve DA requests.
 
-- __FNs/BNs address EDSes by `DataRoot.Hash`.__ The only alternative is by height; however, it does not allow block
-data deduplication in case EDSes are equal and couples the Data layer/pkg with the Header layer/pkg.
+- __FNs/BNs address EDSes by `DataHash`.__ The only alternative is by height; however, it does not allow block
+  data deduplication in case EDSes are equal and couples the Data layer/pkg with the Header layer/pkg.
 
-- __FNs/BNs run a single instance of [`DAGStore`](https://github.com/filecoin-project/dagstore) to manage CAR block
-files.__ DAGStore provides the top-level indexing and CARv2-based indexing per each CAR file. In essence, it's an
-engine for managing any CAR files with indexing, convenient abstractions, tools, recovery mechanisms, etc.
+- __FNs/BNs run a single instance of [`DAGStore`][dagstore] to manage CAR block
+  files.__ DAGStore provides the top-level indexing and CARv2-based indexing per each CAR file. In essence, it's an
+  engine for managing any CAR files with indexing, convenient abstractions, tools, recovery mechanisms, etc.
   - __EDSes as _CARv1_ files over _CARv2_.__ CARv2 encodes indexes into the file, while DAGStore maintains CARv2-based
-indexing. Usage of CARv1 keeps only one copy of the index, stores/transfers less metadata per EDS, and simplifies
-reading EDS from a file.
+      indexing. Usage of CARv1 keeps only one copy of the index, stores/transfers less metadata per EDS, and simplifies
+      reading EDS from a file.
 
 - __LNs DASing remains untouched__. The networking protocol and storage for LNs remain intact as it fulfills the
-requirements. Bitswap usage as the backbone protocol for requesting samples and global Badger KVStore remain unaffected.
+  requirements. Bitswap usage as the backbone protocol for requesting samples and global Badger KVStore remain unaffected.
 
 ### Detailed Design
 
@@ -97,38 +97,37 @@ The central data structure representing Celestia block data is EDS(`rsmt2d.Exten
 is focused around storing entire EDSes as a whole rather than a set of individual chunks, s.t. storage subsystem
 can handle storing and streaming/serving blocks of 4MB and more.
 
-#### EDS Serde
+#### EDS (De-)Serialization
 
-Storing EDS as a whole requires EDS (de)serialization. For this, the [CAR format](https://ipld.io/specs/transport/car) is
-chosen.
+Storing EDS as a whole requires EDS (de)serialization. For this, the [CAR format][car] is chosen.
 
-##### `share.WriteEDS`
+##### `eds.WriteEDS`
 
 To write EDS into a stream/file, `WriteEDS` is introduced. Internally, it
 
-- [Re-imports](https://github.com/celestiaorg/rsmt2d/blob/master/extendeddatasquare.go#L41) EDS similarly to
-[`ipld.ImportShares`](https://github.com/celestiaorg/celestia-node/blob/main/ipld/add.go#L51-L65)
-  - Using [`Blockservice`](https://github.com/ipfs/go-blockservice/blob/master/blockservice.go#L46) with [offline
-exchange](https://github.com/ipfs/go-ipfs-exchange-offline/blob/master/offline.go#L16) and in-memory [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33)
-  - With [`NodeVisitor`](https://github.com/celestiaorg/celestia-node/blob/main/ipld/add.go#L56), which saves to the
-`Blockstore` only NMT Merkle proofs(no shares) _NOTE: `len(node.Links()) == 2`_
+- [Re-imports](https://github.com/celestiaorg/rsmt2d/blob/80d231f733e9dd8ca166c3d670470ed9a1c165d9/extendeddatasquare.go#L44) EDS similarly to
+  [`ipld.ImportShares`](https://github.com/celestiaorg/celestia-node/blob/da4f54bca1bfef86f53880ced569d37ffb4b8b84/share/add.go#L48)
+  - Using [`Blockservice`][blockservice] with [offline
+      exchange][offexchange] and in-memory [`Blockstore`][blockstore]
+  - With [`NodeVisitor`](https://github.com/celestiaorg/celestia-node/blob/da4f54bca1bfef86f53880ced569d37ffb4b8b84/share/add.go#L63), which saves to the
+      [`Blockstore`][blockstore] only NMT Merkle proofs(no shares) _NOTE: `len(node.Links()) == 2`_
     - Actual shares are written further in a particular way explained further
-- Creates and [writes](https://github.com/ipld/go-car/blob/master/car.go#L86) header [`CARv1Header`](https://github.com/ipld/go-car/blob/master/car.go#L30)
+- Creates and [writes](https://github.com/ipld/go-car/blob/dab0fd5bb19dead0da1377270f37be9acf858cf0/car.go#L86) header [`CARv1Header`](https://github.com/ipld/go-car/blob/dab0fd5bb19dead0da1377270f37be9acf858cf0/car.go#L30)
   - Fills up `Roots` field with `EDS.RowRoots/EDS.ColRoots` roots converted into CIDs
 - Iterates over shares in quadrant-by-quadrant order via `EDS.GetCell`
-  - [Writes](https://github.com/ipld/go-car/blob/master/car.go#L118) the shares in row-by-row order
-- Iterates over in-memory Blockstore and [writes]((https://github.com/ipld/go-car/blob/master/car.go#L118)) NMT Merkle
-proofs stored in it
+  - [Writes](https://github.com/ipld/go-car/blob/dab0fd5bb19dead0da1377270f37be9acf858cf0/car.go#L118) the shares in row-by-row order
+- Iterates over in-memory Blockstore and [writes](https://github.com/ipld/go-car/blob/dab0fd5bb19dead0da1377270f37be9acf858cf0/car.go#L118) NMT Merkle
+  proofs stored in it
 
 ___NOTES:___
 
-- _CAR provides [a utility](https://github.com/ipld/go-car/blob/master/car.go#L47) to serialize any DAG into the file
-and there is a way to serialize EDS into DAG(`share/ipld.ImportShares`). This approach is the simplest and traverses
-shares and Merkle Proofs in a depth-first manner packing them in a CAR file. However, this is incompatible with the
-requirement of being able to truncate the CAR file reading out __only__ the first quadrant out of it without NMT proofs,
-so serialization must be different from the utility to support that._
+- _CAR provides [a utility](https://github.com/ipld/go-car/blob/dab0fd5bb19dead0da1377270f37be9acf858cf0/car.go#L47) to serialize any DAG into the file
+  and there is a way to serialize EDS into DAG(`share/ipld.ImportShares`). This approach is the simplest and traverses
+  shares and Merkle Proofs in a depth-first manner packing them in a CAR file. However, this is incompatible with the
+  requirement of being able to truncate the CAR file reading out __only__ the first quadrant out of it without NMT proofs,
+  so serialization must be different from the utility to support that._
 - _Alternatively to `WriteEDS`, an `EDSReader` could be introduced to make EDS-to-stream handling more idiomatic
-and efficient in some cases, with the cost of more complex implementation._
+  and efficient in some cases, with the cost of more complex implementation._
 
 ```go
 // WriteEDS writes the whole EDS into the given io.Writer as CARv1 file.
@@ -136,12 +135,12 @@ and efficient in some cases, with the cost of more complex implementation._
 func WriteEDS(context.Context, *rsmt2d.ExtendedDataSquare, io.Writer) error
 ```
 
-##### `share.ReadEDS`
+##### `eds.ReadEDS`
 
 To read an EDS out of a stream/file, `ReadEDS` is introduced. Internally, it
 
-- Imports EDS with an empty pre-allocated slice. _NOTE: Size can be taken from DataRoot Row/Col len_
-- Wraps given io.Reader with [`BlockReader`](https://github.com/ipld/go-car/blob/master/v2/block_reader.go#L16)
+- Imports EDS with an empty pre-allocated slice. _NOTE: Size can be taken from CARHeader.
+- Wraps given `io.Reader` with [`BlockReader`](https://github.com/ipld/go-car/blob/dab0fd5bb19dead0da1377270f37be9acf858cf0/v2/block_reader.go#L17)
 - Reads out blocks one by one and fills up the EDS quadrant via `EDS.SetCell`
   - In total there should be shares-in-quadrant amount of reads.
 - Recomputes and validates via `EDS.Repair`
@@ -149,24 +148,32 @@ To read an EDS out of a stream/file, `ReadEDS` is introduced. Internally, it
 ```go
 // ReadEDS reads an EDS quadrant(1/4) from an io.Reader CAR file.
 // It expects strictly first EDS quadrant(top left).
-// The returned EDS is guaranteed to be full and valid against the DataRoot, otherwise ReadEDS errors.
-func ReadEDS(context.Context, io.Reader, DataRoot) (*rsmt2d.ExtendedDataSquare, error)
+// The returned EDS is guaranteed to be full and valid against the DataHash, otherwise ReadEDS errors.
+func ReadEDS(context.Context, io.Reader, DataHash) (*rsmt2d.ExtendedDataSquare, error)
 ```
 
-#### `share.EDSStore`
+##### `eds.ODSReader`
 
-FNs/BNs keep an `EDSStore` to manage every EDS on the disk. The `EDSStore` type is introduced in the `share` pkg.
+To read only a quadrant/ODS out of full EDS, `ODSReader` is introduced.
+
+Its constructor wraps any `io.Reader` containing EDS generated by `WriteEDS` and produces an `io.Reader` which reads
+exactly an ODS out of it, similar to `io.LimitReader`. The size of the EDS and ODS can be determined by the amount
+of CIDs in the `CARHeader`.
+
+#### `eds.Store`
+
+FNs/BNs keep an `eds.Store` to manage every EDS on the disk. The `eds.Store` type is introduced in the `eds` pkg.
 Each EDS together with its Merkle Proofs serializes into a CARv1 file. All the serialized CARv1 file blobs are mounted on
 DAGStore via [Local FS Mounts](https://github.com/filecoin-project/dagstore/blob/master/docs/design.md#mounts) and registered
 as [Shards](https://github.com/filecoin-project/dagstore/blob/master/docs/design.md#shards).
 
-The introduced `EDSStore` also maintains (via DAGStore) a top-level index enabling granular and efficient random access
-to every share and/or Merkle proof over every registered CARv1 file. The `EDSStore` provides a custom `Blockstore` interface
+The introduced `eds.Store` also maintains (via DAGStore) a top-level index enabling granular and efficient random access
+to every share and/or Merkle proof over every registered CARv1 file. The `eds.Store` provides a custom `Blockstore` interface
 implementation to achieve access. The main use-case is randomized sampling over the whole chain of EDS block data
 and getting data by namespace.
 
 ```go
-type EDSStore struct {
+type Store struct {
  basepath string 
  dgstr dagstore.DAGStore
  topIdx index.Inverted 
@@ -175,9 +182,9 @@ type EDSStore struct {
  ...
 }
 
-// NewEDSStore constructs EDStore over OS directory to store CARv1 files of EDSes and indices for them.
+// NewStore constructs Store over OS directory to store CARv1 files of EDSes and indices for them.
 // Datastore is used to keep the inverted/top-level index.
-func NewEDSStore(basepath string, ds datastore.Batching) *EDSStore {
+func NewStore(basepath string, ds datastore.Batching) *Store {
  topIdx := index.NewInverted(datastore)
  carIdx := index.NewFSRepo(basepath + "/index")
  mounts := mount.NewRegistry()
@@ -187,7 +194,7 @@ func NewEDSStore(basepath string, ds datastore.Batching) *EDSStore {
   panic(err)
  }
  
- return &EDSStore{
+ return &Store{
   basepath: basepath,
   dgst: dagstore.New(dagstore.Config{...}),
   topIdx: index.NewInverted(datastore),
@@ -199,75 +206,90 @@ func NewEDSStore(basepath string, ds datastore.Batching) *EDSStore {
 
 ___NOTE:___ _EDSStore should have lifecycle methods(Start/Stop)._
 
-##### `share.EDSStore.Put`
+##### `eds.Store.Put`
 
 To write an entire EDS `Put` method is introduced. Internally it
 
-- Opens a file under `Store.Path/DataRoot.Hash` path
+- Opens a file under `Store.Path/DataHash` path
 - Serializes the EDS into the file via `share.WriteEDS`
-- Wraps it with `DAGStore`'s [FileMount](https://github.com/filecoin-project/dagstore/blob/master/mount/file.go#L10)
-- Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12)
+- Wraps it with `DAGStore`'s [FileMount][filemount]
+- Converts `DataHash` to the [`shard.Key`][shardkey]
 - Registers the `Mount` as a `Shard` on the `DAGStore`
 
 ___NOTE:___ _Registering on the DAGStore populates the top-level index with shares/proofs accessible from stored EDS, which is
 out of the scope of the document._
 
 ```go
-// Put stores the given data square with DataRoot's hash as a key.
+// Put stores the given data square with DataHash as a key.
 //
 // The square is verified on the Exchange level and Put only stores the square trusting it.
 // The resulting file stores all the shares and NMT Merkle Proofs of the EDS.
 // Additionally, the file gets indexed s.t. Store.Blockstore can access them. 
-func (s *Store) Put(context.Context, DataRoot, *rsmt2d.ExtendedDataSquare) error
+func (s *Store) Put(context.Context, DataHash, *rsmt2d.ExtendedDataSquare) error
 ```
 
-##### `share.EDSStore.GetCAR`
+##### `eds.Store.GetCAR`
 
 To read an EDS as a byte stream `GetCAR` method is introduced. Internally it
 
-- Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12)
+- Converts `DataHash` to the [`shard.Key`][shardkey]
 - Acquires `ShardAccessor` and returns `io.ReadCloser` from it
 
 ___NOTES:___
 
 - _`DAGStores`'s `ShardAccessor` has to be extended to return an `io.ReadCloser`. Currently, it only returns
   a `Blockstore` of the CAR_
-- _The returned `io.Reader` represents actual EDS exchanged over the wire, which should be limited to return the first quadrant_
+- _The returned `io.ReadCloer` represents full EDS exchanged. To get a quadrant an ODSReader should be used instead_
 
 ```go
-// GetCAR takes a DataRoot and returns a buffered reader to the respective EDS serialized as a CARv1 file.
+// GetCAR takes a DataHash and returns a buffered reader to the respective EDS serialized as a CARv1 file.
 // 
-// The Reader strictly reads the first quadrant(1/4) of EDS, omitting all the NMT Merkle proofs.
-// Integrity of the store data is not verified. 
+// The Reader strictly reads our full EDS, and it's integrity is not verified. 
 // 
 // Caller must Close returned reader after reading.
-func (s *Store) GetCAR(context.Context, DataRoot) (io.ReadCloser, error)
+func (s *Store) GetCAR(context.Context, DataHash) (io.ReadCloser, error)
 ```
 
-##### `share.EDSStore.Blockstore`
+##### `eds.Store.Blockstore`
 
-`Blockstore` method returns a [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33)
-interface implementation instance, providing random access over share and NMT Merkle proof in every stored EDS. It is
-required for FNs/BNs to serve DAS requests over the Bitswap and for [reading data by namespace](#reading-data-by-namespace).
+`Blockstore` method returns a [`Blockstore`][blockstore] interface implementation instance, providing random access over
+share and NMT Merkle proof in every stored EDS. It is required for FNs/BNs to serve DAS requests over the Bitswap.
 
-There is a [frozen/un-merged implementation](https://github.com/filecoin-project/dagstore/pull/116) of `Blockstore`
-over `DAGStore` and CARv2 indexes.
+There is a `Blockstore` over [`DAGStore`][dagstore] and [`CARv2`][carv2] indexes.
 
 ___NOTES:___
 
-- _We can either use it(and finish if something is missing for our case) or implement custom optimized for our needs._
+- _We can either use DAGStore's one or implement custom optimized for our needs._
 - _The Blockstore does not store whole Celestia Blocks, but IPFS blocks. We represent Merkle proofs and shares in IPFS
-blocks._
+  blocks._
+- EDIT: We went with custom implementation.
 
 ```go
 // Blockstore returns an IPFS Blockstore providing access to individual shares/nodes of all EDS 
 // registered on the Store. NOTE: The Blockstore does not store whole Celestia Blocks but IPFS blocks. 
 // We represent `shares` and NMT Merkle proofs as IPFS blocks and IPLD nodes so Bitswap can access those.
 func (s *Store) Blockstore() blockstore.Blockstore
+```
+
+##### `eds.Store.CARBlockstore`
+
+`CARBlockstore` method returns a [`Blockstore`][blockstore] interface implementation instance, providing random access
+over share and NMT Merkle proof in a specific EDS identified by DataHash. It is required for FNs/BNs to enable [reading
+data by namespace](#reading-data-by-namespace).
+
+___NOTES:___
+
+- _The returned Blockstore does not store whole Celestia Blocks, but IPFS blocks. We represent Merkle proofs and shares in IPFS
+  blocks._
 
+```go
+// CARBlockstore returns an IPFS Blockstore providing access to individual shares/nodes of a specific EDS identified by 
+// DataHash and registered on the Store. NOTE: The Blockstore does not store whole Celestia Blocks but IPFS blocks. 
+// We represent `shares` and NMT Merkle proofs as IPFS blocks and IPLD nodes so Bitswap can access those.
+func (s *Store) CARBlockstore(DataHash) (blockstore.Blockstore, error)
 ```
 
-##### `share.EDSStore.Get`
+##### `eds.Store.Get`
 
 To read an entire EDS `Get` method is introduced. Internally it:
 
@@ -277,37 +299,36 @@ To read an entire EDS `Get` method is introduced. Internally it:
 ___NOTE:___ _It's unnecessary, but an API ergonomics/symmetry nice-to-have._
 
 ```go
-// Get reads EDS out of Store by given DataRoot.
+// Get reads EDS out of Store by given DataHash.
 // 
 // It reads only one quadrant(1/4) of the EDS and verifies the integrity of the stored data by recomputing it.
-func (s *Store) Get(context.Context, DataRoot) (*rsmt2d.ExtendedDataSquare, error)
-
+func (s *Store) Get(context.Context, DataHash) (*rsmt2d.ExtendedDataSquare, error)
 ```
 
-##### `share.EDSStore.Has`
+##### `eds.Store.Has`
 
 To check if EDSStore keeps an EDS `Has` method is introduced. Internally it:
 
-- Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12)
-- Checks if [`GetShardInfo`](https://github.com/filecoin-project/dagstore/blob/master/dagstore.go#L483) does not return
-[ErrShardUnknown](https://github.com/filecoin-project/dagstore/blob/eac7733212fdd7c80be5078659f7450b3956d2a6/dagstore.go#L55)
+- Converts `DataHash` to the [`shard.Key`][shardkey]
+- Checks if [`GetShardInfo`](https://github.com/filecoin-project/dagstore/blob/f9e7b7b4594221c8a4840a1e9f3f6e003c1b4c52/dagstore.go#L483) does not return
+  [ErrShardUnknown](https://github.com/filecoin-project/dagstore/blob/eac7733212fdd7c80be5078659f7450b3956d2a6/dagstore.go#L55)
 
 ___NOTE:___ _It's unnecessary, but an API ergonomics/symmetry nice-to-have._
 
 ```go
-// Has checks if EDS exists by the given DataRoot.
-func (s *Store) Has(context.Context, DataRoot) (bool, error)
-
+// Has checks if EDS exists by the given DataHash.
+func (s *Store) Has(context.Context, DataHash) (bool, error)
 ```
 
-##### `share.EDSStore.Remove`
+##### `eds.Store.Remove`
 
 To remove stored EDS `Remove` method is introduced. Internally it:
 
-- Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12)
+- Converts `DataHash` to the [`shard.Key`][shardkey]
 - Destroys `Shard` via `DAGStore`
   - Internally removes its `Mount` as well
-- Removes CARv1 file from disk under `Store.Path/DataRoot.Hash` path
+- Removes CARv1 file from disk under `Store.Path/DataHash` path
+- Drops indecies
 
 ___NOTES:___
 
@@ -315,9 +336,8 @@ ___NOTES:___
 - _GC logic on the DAGStore has to be investigated so that Removing is correctly implemented_
 
 ```go
-// Remove removes EDS from Store by the given DataRoot and cleans up all the indexing.
-func (s *Store) Remove(context.Context, DataRoot) error
-
+// Remove removes EDS from Store by the given DataHash and cleans up all the indexing.
+func (s *Store) Remove(context.Context, DataHash) error
 ```
 
 #### Reading Data By Namespace
@@ -325,8 +345,8 @@ func (s *Store) Remove(context.Context, DataRoot) error
 Generally stays unchanged with minor edits:
 
 - `share/ipld.GetByNamespace` is kept to load data from disk only and not from the network anymore
-  - Using [`Blockservice`](https://github.com/ipfs/go-blockservice/blob/master/blockservice.go#L46) with [offline exchange](https://github.com/ipfs/go-ipfs-exchange-offline/blob/master/offline.go#L16)
-  - Using [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33) provided by `EDSStore`
+  - Using [`Blockservice`][blockservice] with [offline exchange][offexchange]
+  - Using [`Blockstore`][blockstore] provided by `eds.Store`
 - `share/ipld.GetByNamespace` is extended to return NMT Merkle proofs
   - Similar to `share/ipld.GetProofsForShares`
   - Ensure Merkle proofs are not duplicated!
@@ -336,20 +356,20 @@ encoded shares and NMT Merkle Proofs.
 
 #### EDS Deduplication
 
-Addressing EDS by DataRoot allows us to deduplicate equal EDSes. EDS equality is very unlikely to happen in practice,
+Addressing EDS by DataHash allows us to deduplicate equal EDSes. EDS equality is very unlikely to happen in practice,
 beside empty Block case, which always produces the same EDS.
 
 #### Empty Block/EDS
 
 The empty block is valid and small EDS. It can happen in the early stage of the network. Its body is constant, and to avoid
-transferring it over the wire, the `EDSStore` should be pre-initialized with an empty EDS value.
+transferring it over the wire, the `eds.Store` should be pre-initialized with an empty EDS value.
 
 #### EDSStore Directory Path
 
 The EDSStore on construction expects a directory to store CAR files and indices. The path should be gotten based
 on `node.Store.Path`.
 
-### Alternative Approaches
+## Alternative Approaches
 
 - Extended blocks as a set of share blobs and Merkle proofs in global Store (_current approach with KVStore_)
 - Extended block as a single blob only(computing Merkle proofs)
@@ -357,16 +377,29 @@ on `node.Store.Path`.
 - Extended block as a set of DAG/CAR blobs
 - Extended block as a single DAG/CAR blob
 
-### Considerations
+## Considerations
 
 - ___EDS to/from CARv2 converting performance.___ Current sync design assumes two converts from CAR to EDS on the
-protocol layer and back to CAR when storing the EDS. Rsmt2d allocates on most operations with individual shares, and for
-more giant blocks during sync, these allocations put significant pressure on GC. One way to substantially alleviate this
-is to integrate the bytes buffer pool into rmst2d.
+  protocol layer and back to CAR when storing the EDS. Rsmt2d allocates on most operations with individual shares, and for
+  more giant blocks during sync, these allocations put significant pressure on GC. One way to substantially alleviate this
+  is to integrate the bytes buffer pool into rmst2d.
 
 - ___Disk usage increases from the top-level index.___ This is a temporary solution. The index will have to be removed.
-LNs know which block they sample and can provide `DataRoot`'s hash together with sample request over Bitswap, removing
-the need for hash-to-eds-file mapping. This requires us to either facilitate implementation of [Bitswap's auth extension
-](https://github.com/ipfs/specs/pull/270) or proposing a custom Bitswap message extension. Subsequently, the Blockstore
-implementation provided via `EDSStore` would have to be changed to expect `DataRoot`'s hash to be passed through the
-`context.Context`.
+  LNs know which block they sample and can provide `DataHash`together with sample request over Bitswap, removing
+  the need for hash-to-eds-file mapping. This requires us to either facilitate implementation of [Bitswap's auth extension
+  ](https://github.com/ipfs/specs/pull/270) or proposing a custom Bitswap message extension. Subsequently, the Blockstore
+  implementation provided via `eds.Store` would have to be changed to expect DataHash to be passed through the
+  `context.Context`.
+
+[dah]: https://github.com/celestiaorg/celestia-app/blob/86c9bf6b981a8b25033357fddc89ef70abf80681/pkg/da/data_availability_header.go#L28
+[dh]: https://github.com/celestiaorg/celestia-core/blob/f76d026f3525d2d4fa309c62df29d42d33d0e9c6/types/block.go#L354
+[eds]: https://github.com/celestiaorg/rsmt2d/blob/76b270f80f0b9ac966c6f6b043e31514574f90f3/extendeddatasquare.go#L10
+[nmt]: https://github.com/celestiaorg/nmt
+[car]: https://ipld.io/specs/transport/car
+[carv2]: https://ipld.io/specs/transport/car/carv2/
+[dagstore]: https://github.com/filecoin-project/dagstore
+[blockstore]: https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33
+[blockservice]: https://github.com/ipfs/go-blockservice/blob/master/blockservice.go#L46
+[offexchange]: https://github.com/ipfs/go-ipfs-exchange-offline/blob/master/offline.go#L16
+[shardkey]: https://github.com/filecoin-project/dagstore/blob/f9e7b7b4594221c8a4840a1e9f3f6e003c1b4c52/shard/key.go#L12
+[filemount]: https://github.com/filecoin-project/dagstore/blob/f9e7b7b4594221c8a4840a1e9f3f6e003c1b4c52/mount/file.go#L10
diff --git a/go.mod b/go.mod
index 3da051da..bc2ac6cc 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	cosmossdk.io/math v1.0.0-beta.3
 	github.com/BurntSushi/toml v1.2.1
 	github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921
-	github.com/celestiaorg/celestia-app v0.10.0-rc1
+	github.com/celestiaorg/celestia-app v0.10.0
 	github.com/celestiaorg/go-libp2p-messenger v0.1.0
 	github.com/celestiaorg/nmt v0.11.0
 	github.com/celestiaorg/rsmt2d v0.7.0
diff --git a/go.sum b/go.sum
index 032c08b6..f11e9602 100644
--- a/go.sum
+++ b/go.sum
@@ -216,8 +216,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7
 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
 github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
-github.com/celestiaorg/celestia-app v0.10.0-rc1 h1:Uj7wapqGCTBJ49Ub6luoUGwVtFxbEs0n5eDpGD1j/S0=
-github.com/celestiaorg/celestia-app v0.10.0-rc1/go.mod h1:6k/zcNDEgOyJRGnAgWw1VsrwTKcVjOyYG5LPTHcZR+w=
+github.com/celestiaorg/celestia-app v0.10.0 h1:3v9Gz9R89PkSSnXnaavXLDcUaK0u36bnRg9ESgcYc+E=
+github.com/celestiaorg/celestia-app v0.10.0/go.mod h1:6k/zcNDEgOyJRGnAgWw1VsrwTKcVjOyYG5LPTHcZR+w=
 github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20 h1:BqlcOQqL2UqdDTcdCtrOLXDlmwxIA8DiKiY79oahxkQ=
 github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw=
 github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0 h1:65gnQ92mfz+9XNVTHeVwMp+SZuBqmToEnz8+WdDRmQ8=
diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go
index e816a815..73e8c87e 100644
--- a/header/p2p/exchange.go
+++ b/header/p2p/exchange.go
@@ -53,23 +53,34 @@ func NewExchange(
 	if err != nil {
 		return nil, err
 	}
+
 	return &Exchange{
 		host:         host,
 		protocolID:   protocolID(protocolSuffix),
 		trustedPeers: peers,
-		peerTracker:  newPeerTracker(host),
-		Params:       params,
+		peerTracker: newPeerTracker(
+			host,
+			params.MaxAwaitingTime,
+			params.DefaultScore,
+			params.MaxPeerTrackerSize,
+		),
+		Params: params,
 	}, nil
 }
 
 func (ex *Exchange) Start(context.Context) error {
 	ex.ctx, ex.cancel = context.WithCancel(context.Background())
-	go ex.peerTracker.track(ex.ctx)
+
+	go ex.peerTracker.gc()
+	go ex.peerTracker.track()
 	return nil
 }
 
 func (ex *Exchange) Stop(context.Context) error {
+	// cancel the session if it exists
 	ex.cancel()
+	// stop the peerTracker
+	ex.peerTracker.stop()
 	return nil
 }
 
diff --git a/header/p2p/options.go b/header/p2p/options.go
index 27f089aa..0ccf7491 100644
--- a/header/p2p/options.go
+++ b/header/p2p/options.go
@@ -34,17 +34,15 @@ func DefaultServerParameters() *ServerParameters {
 	}
 }
 
-const errSuffix = "value should be positive and non-zero"
-
 func (p *ServerParameters) Validate() error {
 	if p.WriteDeadline == 0 {
-		return fmt.Errorf("invalid write time duration: %s", errSuffix)
+		return fmt.Errorf("invalid write time duration: %v", p.WriteDeadline)
 	}
 	if p.ReadDeadline == 0 {
-		return fmt.Errorf("invalid read time duration: %s", errSuffix)
+		return fmt.Errorf("invalid read time duration: %v", p.ReadDeadline)
 	}
 	if p.MaxRequestSize == 0 {
-		return fmt.Errorf("invalid max request size: %s", errSuffix)
+		return fmt.Errorf("invalid max request size: %d", p.MaxRequestSize)
 	}
 	return nil
 }
@@ -92,6 +90,14 @@ type ClientParameters struct {
 	MaxRequestSize uint64
 	// MaxHeadersPerRequest defines the max amount of headers that can be requested per 1 request.
 	MaxHeadersPerRequest uint64
+	// MaxAwaitingTime specifies the duration that gives to the disconnected peer to be back online,
+	// otherwise it will be removed on the next GC cycle.
+	MaxAwaitingTime time.Duration
+	// DefaultScore specifies the score for newly connected peers.
+	DefaultScore float32
+
+	// MaxTrackerSize specifies the max amount of peers that can be added to the peerTracker.
+	MaxPeerTrackerSize int
 }
 
 // DefaultClientParameters returns the default params to configure the store.
@@ -100,18 +106,41 @@ func DefaultClientParameters() *ClientParameters {
 		MinResponses:         2,
 		MaxRequestSize:       512,
 		MaxHeadersPerRequest: 64,
+		MaxAwaitingTime:      time.Hour,
+		DefaultScore:         1,
+		MaxPeerTrackerSize:   100,
 	}
 }
 
+const (
+	greaterThenZero = "should be greater than 0"
+	providedSuffix  = "Provided value"
+)
+
 func (p *ClientParameters) Validate() error {
 	if p.MinResponses <= 0 {
-		return fmt.Errorf("invalid minimum amount of responses: %s", errSuffix)
+		return fmt.Errorf("invalid MinResponses: %s. %s: %v",
+			greaterThenZero, providedSuffix, p.MinResponses)
 	}
 	if p.MaxRequestSize == 0 {
-		return fmt.Errorf("invalid max request size: %s", errSuffix)
+		return fmt.Errorf("invalid MaxRequestSize: %s. %s: %v", greaterThenZero, providedSuffix, p.MaxRequestSize)
+	}
+	if p.MaxHeadersPerRequest == 0 {
+		return fmt.Errorf("invalid MaxHeadersPerRequest: %s. %s: %v", greaterThenZero, providedSuffix, p.MaxHeadersPerRequest)
+	}
+	if p.MaxHeadersPerRequest > p.MaxRequestSize {
+		return fmt.Errorf("MaxHeadersPerRequest should not be more than MaxRequestSize."+
+			"MaxHeadersPerRequest: %v, MaxRequestSize: %v", p.MaxHeadersPerRequest, p.MaxRequestSize)
+	}
+	if p.MaxAwaitingTime == 0 {
+		return fmt.Errorf("invalid MaxAwaitingTime for peerTracker: "+
+			"%s. %s: %v", greaterThenZero, providedSuffix, p.MaxAwaitingTime)
+	}
+	if p.DefaultScore <= 0 {
+		return fmt.Errorf("invalid DefaultScore: %s. %s: %f", greaterThenZero, providedSuffix, p.DefaultScore)
 	}
-	if p.MaxHeadersPerRequest == 0 || p.MaxHeadersPerRequest > p.MaxRequestSize {
-		return fmt.Errorf("invalid max headers per request: %s", errSuffix)
+	if p.MaxPeerTrackerSize <= 0 {
+		return fmt.Errorf("invalid MaxTrackerSize: %s. %s: %d", greaterThenZero, providedSuffix, p.MaxPeerTrackerSize)
 	}
 	return nil
 }
@@ -138,3 +167,36 @@ func WithMaxHeadersPerRequest[T ClientParameters](amount uint64) Option[T] {
 
 	}
 }
+
+// WithMaxAwaitingTime is a functional option that configures the
+// `MaxAwaitingTime` parameter.
+func WithMaxAwaitingTime[T ClientParameters](duration time.Duration) Option[T] {
+	return func(p *T) {
+		switch t := any(p).(type) { //nolint:gocritic
+		case *ClientParameters:
+			t.MaxAwaitingTime = duration
+		}
+	}
+}
+
+// WithDefaultScore is a functional option that configures the
+// `DefaultScore` parameter.
+func WithDefaultScore[T ClientParameters](score float32) Option[T] {
+	return func(p *T) {
+		switch t := any(p).(type) { //nolint:gocritic
+		case *ClientParameters:
+			t.DefaultScore = score
+		}
+	}
+}
+
+// WithMaxTrackerSize is a functional option that configures the
+// `MaxTrackerSize` parameter.
+func WithMaxTrackerSize[T ClientParameters](size int) Option[T] {
+	return func(p *T) {
+		switch t := any(p).(type) { //nolint:gocritic
+		case *ClientParameters:
+			t.MaxPeerTrackerSize = size
+		}
+	}
+}
diff --git a/header/p2p/peer_stats.go b/header/p2p/peer_stats.go
index 37f905e3..91c5b939 100644
--- a/header/p2p/peer_stats.go
+++ b/header/p2p/peer_stats.go
@@ -4,6 +4,7 @@ import (
 	"container/heap"
 	"context"
 	"sync"
+	"time"
 
 	"github.com/libp2p/go-libp2p-core/peer"
 )
@@ -14,6 +15,9 @@ type peerStat struct {
 	peerID peer.ID
 	// score is the average speed per single request
 	peerScore float32
+	// pruneDeadline specifies when disconnected peer will be removed if
+	// it does not return online.
+	pruneDeadline time.Time
 }
 
 // updateStats recalculates peer.score by averaging the last score
diff --git a/header/p2p/peer_tracker.go b/header/p2p/peer_tracker.go
index 9451b6f9..3d5aa9c4 100644
--- a/header/p2p/peer_tracker.go
+++ b/header/p2p/peer_tracker.go
@@ -3,6 +3,7 @@ package p2p
 import (
 	"context"
 	"sync"
+	"time"
 
 	"github.com/libp2p/go-libp2p-core/event"
 	"github.com/libp2p/go-libp2p-core/host"
@@ -10,25 +11,61 @@ import (
 	"github.com/libp2p/go-libp2p-core/peer"
 )
 
+// gcCycle defines the duration after which the peerTracker starts removing peers.
+var gcCycle = time.Minute * 30
+
 type peerTracker struct {
-	sync.RWMutex
-	connectedPeers map[peer.ID]*peerStat
+	host host.Host
+
+	peerLk sync.RWMutex
+	// trackedPeers contains active peers that we can request to.
 	// we cache the peer once they disconnect,
 	// so we can guarantee that peerQueue will only contain active peers
+	trackedPeers map[peer.ID]*peerStat
+	// disconnectedPeers contains disconnected peers. In case if peer does not return
+	// online until pruneDeadline, it will be removed and its score will be lost.
 	disconnectedPeers map[peer.ID]*peerStat
 
-	host host.Host
+	// maxAwaitingTime specifies the duration that gives to the disconnected peer to be back online,
+	// otherwise it will be removed on the next GC cycle.
+	maxAwaitingTime time.Duration
+	// defaultScore specifies the score for newly connected peers.
+	defaultScore float32
+	// maxPeerTrackerSize specifies the max amount of peers that can be added to the peerTracker.
+	maxPeerTrackerSize int
+
+	ctx    context.Context
+	cancel context.CancelFunc
+	// done is used to gracefully stop the peerTracker.
+	// It allows to wait until track() and gc() will be stopped.
+	done chan struct{}
 }
 
-func newPeerTracker(h host.Host) *peerTracker {
+func newPeerTracker(
+	h host.Host,
+	maxAwaitingTime time.Duration,
+	defaultScore float32,
+	maxPeerTrackerSize int,
+) *peerTracker {
+	ctx, cancel := context.WithCancel(context.Background())
 	return &peerTracker{
-		disconnectedPeers: make(map[peer.ID]*peerStat),
-		connectedPeers:    make(map[peer.ID]*peerStat),
-		host:              h,
+		host:               h,
+		disconnectedPeers:  make(map[peer.ID]*peerStat),
+		trackedPeers:       make(map[peer.ID]*peerStat),
+		maxAwaitingTime:    maxAwaitingTime,
+		defaultScore:       defaultScore,
+		maxPeerTrackerSize: maxPeerTrackerSize,
+		ctx:                ctx,
+		cancel:             cancel,
+		done:               make(chan struct{}, 2),
 	}
 }
 
-func (p *peerTracker) track(ctx context.Context) {
+func (p *peerTracker) track() {
+	defer func() {
+		p.done <- struct{}{}
+	}()
+
 	// store peers that have been already connected
 	for _, peer := range p.host.Peerstore().Peers() {
 		p.connected(peer)
@@ -39,9 +76,10 @@ func (p *peerTracker) track(ctx context.Context) {
 		log.Errorw("subscribing to EvtPeerConnectednessChanged", "err", err)
 		return
 	}
+
 	for {
 		select {
-		case <-ctx.Done():
+		case <-p.ctx.Done():
 			err = subs.Close()
 			if err != nil {
 				log.Errorw("closing subscription", "err", err)
@@ -63,42 +101,92 @@ func (p *peerTracker) connected(pID peer.ID) {
 	if p.host.ID() == pID {
 		return
 	}
+
 	for _, c := range p.host.Network().ConnsToPeer(pID) {
 		// check if connection is short-termed and skip this peer
 		if c.Stat().Transient {
 			return
 		}
 	}
-	p.Lock()
-	defer p.Unlock()
-	// additional check in p.connectedPeers should be done,
+
+	p.peerLk.Lock()
+	defer p.peerLk.Unlock()
+	// skip adding the peer to avoid overfilling of the peerTracker with unused peers if:
+	// peerTracker reaches the maxTrackerSize and there are more connected peers
+	// than disconnected peers.
+	if len(p.trackedPeers)+len(p.disconnectedPeers) > p.maxPeerTrackerSize &&
+		len(p.trackedPeers) > len(p.disconnectedPeers) {
+		return
+	}
+
+	// additional check in p.trackedPeers should be done,
 	// because libp2p does not emit multiple Connected events per 1 peer
 	stats, ok := p.disconnectedPeers[pID]
 	if !ok {
-		stats = &peerStat{peerID: pID}
+		stats = &peerStat{peerID: pID, peerScore: p.defaultScore}
 	} else {
 		delete(p.disconnectedPeers, pID)
 	}
-	p.connectedPeers[pID] = stats
+	p.trackedPeers[pID] = stats
 }
 
 func (p *peerTracker) disconnected(pID peer.ID) {
-	p.Lock()
-	defer p.Unlock()
-	stats, ok := p.connectedPeers[pID]
+	p.peerLk.Lock()
+	defer p.peerLk.Unlock()
+	stats, ok := p.trackedPeers[pID]
 	if !ok {
 		return
 	}
+	stats.pruneDeadline = time.Now().Add(p.maxAwaitingTime)
 	p.disconnectedPeers[pID] = stats
-	delete(p.connectedPeers, pID)
+	delete(p.trackedPeers, pID)
 }
 
 func (p *peerTracker) peers() []*peerStat {
-	p.RLock()
-	defer p.RUnlock()
-	peers := make([]*peerStat, 0, len(p.connectedPeers))
-	for _, stat := range p.connectedPeers {
+	p.peerLk.RLock()
+	defer p.peerLk.RUnlock()
+	peers := make([]*peerStat, 0, len(p.trackedPeers))
+	for _, stat := range p.trackedPeers {
 		peers = append(peers, stat)
 	}
 	return peers
 }
+
+// gc goes through connected and disconnected peers once every gcPeriod
+// and removes:
+// * disconnected peers which have been disconnected for more than maxAwaitingTime;
+// * connected peers whose scores are less than or equal than defaultScore;
+func (p *peerTracker) gc() {
+	ticker := time.NewTicker(gcCycle)
+	for {
+		select {
+		case <-p.ctx.Done():
+			p.done <- struct{}{}
+			return
+		case <-ticker.C:
+			p.peerLk.Lock()
+			now := time.Now()
+			for id, peer := range p.disconnectedPeers {
+				if peer.pruneDeadline.Before(now) {
+					delete(p.disconnectedPeers, id)
+				}
+			}
+
+			for id, peer := range p.trackedPeers {
+				if peer.peerScore <= p.defaultScore {
+					delete(p.trackedPeers, id)
+				}
+			}
+			p.peerLk.Unlock()
+		}
+	}
+}
+
+// stop waits until all background routines will be finished.
+func (p *peerTracker) stop() {
+	p.cancel()
+
+	for i := 0; i < cap(p.done); i++ {
+		<-p.done
+	}
+}
diff --git a/nodebuilder/das/constructors.go b/nodebuilder/das/constructors.go
index d6fa19f5..b6b44758 100644
--- a/nodebuilder/das/constructors.go
+++ b/nodebuilder/das/constructors.go
@@ -37,8 +37,8 @@ func NewDASer(
 	hsub header.Subscriber,
 	store header.Store,
 	batching datastore.Batching,
-	fraudService fraud.Service,
+	fraudServ fraud.Service,
 	options ...das.Option,
 ) (*das.DASer, error) {
-	return das.NewDASer(da, hsub, store, batching, fraudService, options...)
+	return das.NewDASer(da, hsub, store, batching, fraudServ, options...)
 }
diff --git a/nodebuilder/das/das.go b/nodebuilder/das/das.go
index 5d2b58b1..f1dddbc4 100644
--- a/nodebuilder/das/das.go
+++ b/nodebuilder/das/das.go
@@ -6,6 +6,8 @@ import (
 	"github.com/celestiaorg/celestia-node/das"
 )
 
+var _ Module = (*API)(nil)
+
 //go:generate mockgen -destination=mocks/api.go -package=mocks . Module
 type Module interface {
 	// SamplingStats returns the current statistics over the DA sampling process.
@@ -17,6 +19,16 @@ type Module interface {
 // API is a wrapper around Module for the RPC.
 // TODO(@distractedm1nd): These structs need to be autogenerated.
 type API struct {
-	SamplingStats func(ctx context.Context) (das.SamplingStats, error)
-	WaitCatchUp   func(ctx context.Context) error
+	Internal struct {
+		SamplingStats func(ctx context.Context) (das.SamplingStats, error) `perm:"read"`
+		WaitCatchUp   func(ctx context.Context) error                      `perm:"read"`
+	}
+}
+
+func (api *API) SamplingStats(ctx context.Context) (das.SamplingStats, error) {
+	return api.Internal.SamplingStats(ctx)
+}
+
+func (api *API) WaitCatchUp(ctx context.Context) error {
+	return api.Internal.WaitCatchUp(ctx)
 }
diff --git a/nodebuilder/default_services.go b/nodebuilder/default_services.go
index c41af050..969688ce 100644
--- a/nodebuilder/default_services.go
+++ b/nodebuilder/default_services.go
@@ -1,19 +1,21 @@
 package nodebuilder
 
 import (
-	"github.com/celestiaorg/celestia-node/das"
-	"github.com/celestiaorg/celestia-node/fraud"
+	"github.com/celestiaorg/celestia-node/nodebuilder/das"
+	"github.com/celestiaorg/celestia-node/nodebuilder/fraud"
 	"github.com/celestiaorg/celestia-node/nodebuilder/header"
-	"github.com/celestiaorg/celestia-node/share/availability/light"
-	"github.com/celestiaorg/celestia-node/state"
+	"github.com/celestiaorg/celestia-node/nodebuilder/p2p"
+	"github.com/celestiaorg/celestia-node/nodebuilder/share"
+	"github.com/celestiaorg/celestia-node/nodebuilder/state"
 )
 
-// PackageToDefaultImpl maps a package to its default implementation. Currently only used for
+// PackageToAPI maps a package to its API struct. Currently only used for
 // method discovery for openrpc spec generation
-var PackageToDefaultImpl = map[string]interface{}{
-	"fraud":  &fraud.ProofService{},
-	"state":  &state.CoreAccessor{},
-	"share":  &light.ShareAvailability{},
-	"header": &header.Service{},
-	"daser":  &das.DASer{},
+var PackageToAPI = map[string]interface{}{
+	"fraud":  &fraud.API{},
+	"state":  &state.API{},
+	"share":  &share.API{},
+	"header": &header.API{},
+	"daser":  &das.API{},
+	"p2p":    &p2p.API{},
 }
diff --git a/nodebuilder/fraud/fraud.go b/nodebuilder/fraud/fraud.go
index 5eacd0db..fd9cb3ed 100644
--- a/nodebuilder/fraud/fraud.go
+++ b/nodebuilder/fraud/fraud.go
@@ -6,20 +6,32 @@ import (
 	"github.com/celestiaorg/celestia-node/fraud"
 )
 
+var _ Module = (*API)(nil)
+
 // Module encompasses the behavior necessary to subscribe and broadcast fraud proofs within the
 // network. Any method signature changed here needs to also be changed in the API struct.
 //
 //go:generate mockgen -destination=mocks/api.go -package=mocks . Module
 type Module interface {
-	// Get fetches fraud proofs from the disk by its type.
-	Get(context.Context, fraud.ProofType) ([]Proof, error)
 	// Subscribe allows to subscribe on a Proof pub sub topic by its type.
 	Subscribe(context.Context, fraud.ProofType) (<-chan Proof, error)
+	// Get fetches fraud proofs from the disk by its type.
+	Get(context.Context, fraud.ProofType) ([]Proof, error)
 }
 
 // API is a wrapper around Module for the RPC.
 // TODO(@distractedm1nd): These structs need to be autogenerated.
 type API struct {
-	Subscribe func(context.Context, fraud.ProofType) (<-chan Proof, error)
-	Get       func(context.Context, fraud.ProofType) ([]Proof, error)
+	Internal struct {
+		Subscribe func(context.Context, fraud.ProofType) (<-chan Proof, error) `perm:"public"`
+		Get       func(context.Context, fraud.ProofType) ([]Proof, error)      `perm:"public"`
+	}
+}
+
+func (api *API) Subscribe(ctx context.Context, proofType fraud.ProofType) (<-chan Proof, error) {
+	return api.Internal.Subscribe(ctx, proofType)
+}
+
+func (api *API) Get(ctx context.Context, proofType fraud.ProofType) ([]Proof, error) {
+	return api.Internal.Get(ctx, proofType)
 }
diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go
index cc85faec..082285dd 100644
--- a/nodebuilder/header/header.go
+++ b/nodebuilder/header/header.go
@@ -17,13 +17,27 @@ type Module interface {
 	// Head returns the ExtendedHeader of the chain head.
 	Head(context.Context) (*header.ExtendedHeader, error)
 	// IsSyncing returns the status of sync
-	IsSyncing() bool
+	IsSyncing(context.Context) bool
 }
 
 // API is a wrapper around Module for the RPC.
 // TODO(@distractedm1nd): These structs need to be autogenerated.
 type API struct {
-	GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error)
-	Head        func(context.Context) (*header.ExtendedHeader, error)
-	IsSyncing   func() bool
+	Internal struct {
+		GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"public"`
+		Head        func(context.Context) (*header.ExtendedHeader, error)         `perm:"public"`
+		IsSyncing   func(context.Context) bool                                    `perm:"public"`
+	}
+}
+
+func (api *API) GetByHeight(ctx context.Context, u uint64) (*header.ExtendedHeader, error) {
+	return api.Internal.GetByHeight(ctx, u)
+}
+
+func (api *API) Head(ctx context.Context) (*header.ExtendedHeader, error) {
+	return api.Internal.Head(ctx)
+}
+
+func (api *API) IsSyncing(ctx context.Context) bool {
+	return api.Internal.IsSyncing(ctx)
 }
diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go
index 6f139979..ae596d61 100644
--- a/nodebuilder/header/module.go
+++ b/nodebuilder/header/module.go
@@ -112,6 +112,9 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option {
 						p2p.WithMinResponses(cfg.Client.MinResponses),
 						p2p.WithMaxRequestSize[p2p.ClientParameters](cfg.Client.MaxRequestSize),
 						p2p.WithMaxHeadersPerRequest(cfg.Client.MaxHeadersPerRequest),
+						p2p.WithMaxAwaitingTime(cfg.Client.MaxAwaitingTime),
+						p2p.WithDefaultScore(cfg.Client.DefaultScore),
+						p2p.WithMaxTrackerSize(cfg.Client.MaxPeerTrackerSize),
 					}
 				},
 			),
diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go
index b569df00..1cdaaf17 100644
--- a/nodebuilder/header/service.go
+++ b/nodebuilder/header/service.go
@@ -44,6 +44,6 @@ func (s *Service) Head(ctx context.Context) (*header.ExtendedHeader, error) {
 	return s.store.Head(ctx)
 }
 
-func (s *Service) IsSyncing() bool {
+func (s *Service) IsSyncing(context.Context) bool {
 	return !s.syncer.State().Finished()
 }
diff --git a/nodebuilder/module.go b/nodebuilder/module.go
index 7a72e3bb..5f1f3e0c 100644
--- a/nodebuilder/module.go
+++ b/nodebuilder/module.go
@@ -30,6 +30,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store
 		fx.Supply(store.Config),
 		fx.Provide(store.Datastore),
 		fx.Provide(store.Keystore),
+		fx.Supply(node.StorePath(store.Path())),
 		// modules provided by the node
 		p2p.ConstructModule(tp, &cfg.P2P),
 		state.ConstructModule(tp, &cfg.State),
diff --git a/nodebuilder/node/type.go b/nodebuilder/node/type.go
index 6ba30487..7af1c56b 100644
--- a/nodebuilder/node/type.go
+++ b/nodebuilder/node/type.go
@@ -4,6 +4,9 @@ package node
 // The zero value for Type is invalid.
 type Type uint8
 
+// StorePath is an alias used in order to pass the base path of the node store to nodebuilder modules.
+type StorePath string
+
 const (
 	// Bridge is a Celestia Node that bridges the Celestia consensus network and data availability
 	// network. It maintains a trusted channel/connection to a Celestia Core node via the core.Client
diff --git a/nodebuilder/p2p/mocks/api.go b/nodebuilder/p2p/mocks/api.go
new file mode 100644
index 00000000..bf5f7a0a
--- /dev/null
+++ b/nodebuilder/p2p/mocks/api.go
@@ -0,0 +1,292 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/celestiaorg/celestia-node/nodebuilder/p2p (interfaces: Module)
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+	context "context"
+	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
+	metrics "github.com/libp2p/go-libp2p-core/metrics"
+	network "github.com/libp2p/go-libp2p-core/network"
+	peer "github.com/libp2p/go-libp2p-core/peer"
+	protocol "github.com/libp2p/go-libp2p-core/protocol"
+	rcmgr "github.com/libp2p/go-libp2p-resource-manager"
+)
+
+// MockModule is a mock of Module interface.
+type MockModule struct {
+	ctrl     *gomock.Controller
+	recorder *MockModuleMockRecorder
+}
+
+// MockModuleMockRecorder is the mock recorder for MockModule.
+type MockModuleMockRecorder struct {
+	mock *MockModule
+}
+
+// NewMockModule creates a new mock instance.
+func NewMockModule(ctrl *gomock.Controller) *MockModule {
+	mock := &MockModule{ctrl: ctrl}
+	mock.recorder = &MockModuleMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockModule) EXPECT() *MockModuleMockRecorder {
+	return m.recorder
+}
+
+// BandwidthForPeer mocks base method.
+func (m *MockModule) BandwidthForPeer(arg0 context.Context, arg1 peer.ID) metrics.Stats {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "BandwidthForPeer", arg0, arg1)
+	ret0, _ := ret[0].(metrics.Stats)
+	return ret0
+}
+
+// BandwidthForPeer indicates an expected call of BandwidthForPeer.
+func (mr *MockModuleMockRecorder) BandwidthForPeer(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthForPeer", reflect.TypeOf((*MockModule)(nil).BandwidthForPeer), arg0, arg1)
+}
+
+// BandwidthForProtocol mocks base method.
+func (m *MockModule) BandwidthForProtocol(arg0 context.Context, arg1 protocol.ID) metrics.Stats {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "BandwidthForProtocol", arg0, arg1)
+	ret0, _ := ret[0].(metrics.Stats)
+	return ret0
+}
+
+// BandwidthForProtocol indicates an expected call of BandwidthForProtocol.
+func (mr *MockModuleMockRecorder) BandwidthForProtocol(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthForProtocol", reflect.TypeOf((*MockModule)(nil).BandwidthForProtocol), arg0, arg1)
+}
+
+// BandwidthStats mocks base method.
+func (m *MockModule) BandwidthStats(arg0 context.Context) metrics.Stats {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "BandwidthStats", arg0)
+	ret0, _ := ret[0].(metrics.Stats)
+	return ret0
+}
+
+// BandwidthStats indicates an expected call of BandwidthStats.
+func (mr *MockModuleMockRecorder) BandwidthStats(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthStats", reflect.TypeOf((*MockModule)(nil).BandwidthStats), arg0)
+}
+
+// BlockPeer mocks base method.
+func (m *MockModule) BlockPeer(arg0 context.Context, arg1 peer.ID) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "BlockPeer", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// BlockPeer indicates an expected call of BlockPeer.
+func (mr *MockModuleMockRecorder) BlockPeer(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockPeer", reflect.TypeOf((*MockModule)(nil).BlockPeer), arg0, arg1)
+}
+
+// ClosePeer mocks base method.
+func (m *MockModule) ClosePeer(arg0 context.Context, arg1 peer.ID) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ClosePeer", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// ClosePeer indicates an expected call of ClosePeer.
+func (mr *MockModuleMockRecorder) ClosePeer(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClosePeer", reflect.TypeOf((*MockModule)(nil).ClosePeer), arg0, arg1)
+}
+
+// Connect mocks base method.
+func (m *MockModule) Connect(arg0 context.Context, arg1 peer.AddrInfo) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Connect", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Connect indicates an expected call of Connect.
+func (mr *MockModuleMockRecorder) Connect(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connect", reflect.TypeOf((*MockModule)(nil).Connect), arg0, arg1)
+}
+
+// Connectedness mocks base method.
+func (m *MockModule) Connectedness(arg0 context.Context, arg1 peer.ID) network.Connectedness {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Connectedness", arg0, arg1)
+	ret0, _ := ret[0].(network.Connectedness)
+	return ret0
+}
+
+// Connectedness indicates an expected call of Connectedness.
+func (mr *MockModuleMockRecorder) Connectedness(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connectedness", reflect.TypeOf((*MockModule)(nil).Connectedness), arg0, arg1)
+}
+
+// Info mocks base method.
+func (m *MockModule) Info(arg0 context.Context) peer.AddrInfo {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Info", arg0)
+	ret0, _ := ret[0].(peer.AddrInfo)
+	return ret0
+}
+
+// Info indicates an expected call of Info.
+func (mr *MockModuleMockRecorder) Info(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockModule)(nil).Info), arg0)
+}
+
+// IsProtected mocks base method.
+func (m *MockModule) IsProtected(arg0 context.Context, arg1 peer.ID, arg2 string) bool {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsProtected", arg0, arg1, arg2)
+	ret0, _ := ret[0].(bool)
+	return ret0
+}
+
+// IsProtected indicates an expected call of IsProtected.
+func (mr *MockModuleMockRecorder) IsProtected(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsProtected", reflect.TypeOf((*MockModule)(nil).IsProtected), arg0, arg1, arg2)
+}
+
+// ListBlockedPeers mocks base method.
+func (m *MockModule) ListBlockedPeers(arg0 context.Context) []peer.ID {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ListBlockedPeers", arg0)
+	ret0, _ := ret[0].([]peer.ID)
+	return ret0
+}
+
+// ListBlockedPeers indicates an expected call of ListBlockedPeers.
+func (mr *MockModuleMockRecorder) ListBlockedPeers(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBlockedPeers", reflect.TypeOf((*MockModule)(nil).ListBlockedPeers), arg0)
+}
+
+// NATStatus mocks base method.
+func (m *MockModule) NATStatus(arg0 context.Context) (network.Reachability, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "NATStatus", arg0)
+	ret0, _ := ret[0].(network.Reachability)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// NATStatus indicates an expected call of NATStatus.
+func (mr *MockModuleMockRecorder) NATStatus(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NATStatus", reflect.TypeOf((*MockModule)(nil).NATStatus), arg0)
+}
+
+// PeerInfo mocks base method.
+func (m *MockModule) PeerInfo(arg0 context.Context, arg1 peer.ID) peer.AddrInfo {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "PeerInfo", arg0, arg1)
+	ret0, _ := ret[0].(peer.AddrInfo)
+	return ret0
+}
+
+// PeerInfo indicates an expected call of PeerInfo.
+func (mr *MockModuleMockRecorder) PeerInfo(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeerInfo", reflect.TypeOf((*MockModule)(nil).PeerInfo), arg0, arg1)
+}
+
+// Peers mocks base method.
+func (m *MockModule) Peers(arg0 context.Context) []peer.ID {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Peers", arg0)
+	ret0, _ := ret[0].([]peer.ID)
+	return ret0
+}
+
+// Peers indicates an expected call of Peers.
+func (mr *MockModuleMockRecorder) Peers(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peers", reflect.TypeOf((*MockModule)(nil).Peers), arg0)
+}
+
+// Protect mocks base method.
+func (m *MockModule) Protect(arg0 context.Context, arg1 peer.ID, arg2 string) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "Protect", arg0, arg1, arg2)
+}
+
+// Protect indicates an expected call of Protect.
+func (mr *MockModuleMockRecorder) Protect(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Protect", reflect.TypeOf((*MockModule)(nil).Protect), arg0, arg1, arg2)
+}
+
+// PubSubPeers mocks base method.
+func (m *MockModule) PubSubPeers(arg0 context.Context, arg1 string) []peer.ID {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "PubSubPeers", arg0, arg1)
+	ret0, _ := ret[0].([]peer.ID)
+	return ret0
+}
+
+// PubSubPeers indicates an expected call of PubSubPeers.
+func (mr *MockModuleMockRecorder) PubSubPeers(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PubSubPeers", reflect.TypeOf((*MockModule)(nil).PubSubPeers), arg0, arg1)
+}
+
+// ResourceState mocks base method.
+func (m *MockModule) ResourceState(arg0 context.Context) (rcmgr.ResourceManagerStat, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ResourceState", arg0)
+	ret0, _ := ret[0].(rcmgr.ResourceManagerStat)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ResourceState indicates an expected call of ResourceState.
+func (mr *MockModuleMockRecorder) ResourceState(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceState", reflect.TypeOf((*MockModule)(nil).ResourceState), arg0)
+}
+
+// UnblockPeer mocks base method.
+func (m *MockModule) UnblockPeer(arg0 context.Context, arg1 peer.ID) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "UnblockPeer", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// UnblockPeer indicates an expected call of UnblockPeer.
+func (mr *MockModuleMockRecorder) UnblockPeer(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnblockPeer", reflect.TypeOf((*MockModule)(nil).UnblockPeer), arg0, arg1)
+}
+
+// Unprotect mocks base method.
+func (m *MockModule) Unprotect(arg0 context.Context, arg1 peer.ID, arg2 string) bool {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Unprotect", arg0, arg1, arg2)
+	ret0, _ := ret[0].(bool)
+	return ret0
+}
+
+// Unprotect indicates an expected call of Unprotect.
+func (mr *MockModuleMockRecorder) Unprotect(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unprotect", reflect.TypeOf((*MockModule)(nil).Unprotect), arg0, arg1, arg2)
+}
diff --git a/nodebuilder/p2p/p2p.go b/nodebuilder/p2p/p2p.go
index e27e3003..5421f12d 100644
--- a/nodebuilder/p2p/p2p.go
+++ b/nodebuilder/p2p/p2p.go
@@ -16,63 +16,66 @@ import (
 	"github.com/libp2p/go-libp2p/p2p/net/conngater"
 )
 
+var _ Module = (*API)(nil)
+
 // Module represents all accessible methods related to the node's p2p
 // host / operations.
 //
 //nolint:dupl
+//go:generate mockgen -destination=mocks/api.go -package=mocks . Module
 type Module interface {
 	// Info returns address information about the host.
-	Info() peer.AddrInfo
+	Info(context.Context) peer.AddrInfo
 	// Peers returns all peer IDs used across all inner stores.
-	Peers() []peer.ID
+	Peers(context.Context) []peer.ID
 	// PeerInfo returns a small slice of information Peerstore has on the
 	// given peer.
-	PeerInfo(id peer.ID) peer.AddrInfo
+	PeerInfo(ctx context.Context, id peer.ID) peer.AddrInfo
 
 	// Connect ensures there is a connection between this host and the peer with
 	// given peer.
 	Connect(ctx context.Context, pi peer.AddrInfo) error
 	// ClosePeer closes the connection to a given peer.
-	ClosePeer(id peer.ID) error
+	ClosePeer(ctx context.Context, id peer.ID) error
 	// Connectedness returns a state signaling connection capabilities.
-	Connectedness(id peer.ID) network.Connectedness
+	Connectedness(ctx context.Context, id peer.ID) network.Connectedness
 	// NATStatus returns the current NAT status.
-	NATStatus() (network.Reachability, error)
+	NATStatus(context.Context) (network.Reachability, error)
 
 	// BlockPeer adds a peer to the set of blocked peers.
-	BlockPeer(p peer.ID) error
+	BlockPeer(ctx context.Context, p peer.ID) error
 	// UnblockPeer removes a peer from the set of blocked peers.
-	UnblockPeer(p peer.ID) error
+	UnblockPeer(ctx context.Context, p peer.ID) error
 	// ListBlockedPeers returns a list of blocked peers.
-	ListBlockedPeers() []peer.ID
+	ListBlockedPeers(context.Context) []peer.ID
 	// Protect adds a peer to the list of peers who have a bidirectional
 	// peering agreement that they are protected from being trimmed, dropped
 	// or negatively scored.
-	Protect(id peer.ID, tag string)
+	Protect(ctx context.Context, id peer.ID, tag string)
 	// Unprotect removes a peer from the list of peers who have a bidirectional
 	// peering agreement that they are protected from being trimmed, dropped
 	// or negatively scored, returning a bool representing whether the given
 	// peer is protected or not.
-	Unprotect(id peer.ID, tag string) bool
+	Unprotect(ctx context.Context, id peer.ID, tag string) bool
 	// IsProtected returns whether the given peer is protected.
-	IsProtected(id peer.ID, tag string) bool
+	IsProtected(ctx context.Context, id peer.ID, tag string) bool
 
 	// BandwidthStats returns a Stats struct with bandwidth metrics for all
 	// data sent/received by the local peer, regardless of protocol or remote
 	// peer IDs.
-	BandwidthStats() metrics.Stats
+	BandwidthStats(context.Context) metrics.Stats
 	// BandwidthForPeer returns a Stats struct with bandwidth metrics associated with the given peer.ID.
 	// The metrics returned include all traffic sent / received for the peer, regardless of protocol.
-	BandwidthForPeer(id peer.ID) metrics.Stats
+	BandwidthForPeer(ctx context.Context, id peer.ID) metrics.Stats
 	// BandwidthForProtocol returns a Stats struct with bandwidth metrics associated with the given protocol.ID.
-	BandwidthForProtocol(proto protocol.ID) metrics.Stats
+	BandwidthForProtocol(ctx context.Context, proto protocol.ID) metrics.Stats
 
 	// ResourceState returns the state of the resource manager.
-	ResourceState() (rcmgr.ResourceManagerStat, error)
+	ResourceState(context.Context) (rcmgr.ResourceManagerStat, error)
 
 	// PubSubPeers returns the peer IDs of the peers joined on
 	// the given topic.
-	PubSubPeers(topic string) []peer.ID
+	PubSubPeers(ctx context.Context, topic string) []peer.ID
 }
 
 // module contains all components necessary to access information and
@@ -101,15 +104,15 @@ func newModule(
 	}
 }
 
-func (m *module) Info() peer.AddrInfo {
+func (m *module) Info(context.Context) peer.AddrInfo {
 	return *libhost.InfoFromHost(m.host)
 }
 
-func (m *module) Peers() []peer.ID {
+func (m *module) Peers(context.Context) []peer.ID {
 	return m.host.Peerstore().Peers()
 }
 
-func (m *module) PeerInfo(id peer.ID) peer.AddrInfo {
+func (m *module) PeerInfo(_ context.Context, id peer.ID) peer.AddrInfo {
 	return m.host.Peerstore().PeerInfo(id)
 }
 
@@ -117,15 +120,15 @@ func (m *module) Connect(ctx context.Context, pi peer.AddrInfo) error {
 	return m.host.Connect(ctx, pi)
 }
 
-func (m *module) ClosePeer(id peer.ID) error {
+func (m *module) ClosePeer(_ context.Context, id peer.ID) error {
 	return m.host.Network().ClosePeer(id)
 }
 
-func (m *module) Connectedness(id peer.ID) network.Connectedness {
+func (m *module) Connectedness(_ context.Context, id peer.ID) network.Connectedness {
 	return m.host.Network().Connectedness(id)
 }
 
-func (m *module) NATStatus() (network.Reachability, error) {
+func (m *module) NATStatus(context.Context) (network.Reachability, error) {
 	basic, ok := m.host.(*basichost.BasicHost)
 	if !ok {
 		return 0, fmt.Errorf("unexpected implementation of host.Host, expected %s, got %T",
@@ -134,43 +137,43 @@ func (m *module) NATStatus() (network.Reachability, error) {
 	return basic.GetAutoNat().Status(), nil
 }
 
-func (m *module) BlockPeer(p peer.ID) error {
+func (m *module) BlockPeer(_ context.Context, p peer.ID) error {
 	return m.connGater.BlockPeer(p)
 }
 
-func (m *module) UnblockPeer(p peer.ID) error {
+func (m *module) UnblockPeer(_ context.Context, p peer.ID) error {
 	return m.connGater.UnblockPeer(p)
 }
 
-func (m *module) ListBlockedPeers() []peer.ID {
+func (m *module) ListBlockedPeers(context.Context) []peer.ID {
 	return m.connGater.ListBlockedPeers()
 }
 
-func (m *module) Protect(id peer.ID, tag string) {
+func (m *module) Protect(_ context.Context, id peer.ID, tag string) {
 	m.host.ConnManager().Protect(id, tag)
 }
 
-func (m *module) Unprotect(id peer.ID, tag string) bool {
+func (m *module) Unprotect(_ context.Context, id peer.ID, tag string) bool {
 	return m.host.ConnManager().Unprotect(id, tag)
 }
 
-func (m *module) IsProtected(id peer.ID, tag string) bool {
+func (m *module) IsProtected(_ context.Context, id peer.ID, tag string) bool {
 	return m.host.ConnManager().IsProtected(id, tag)
 }
 
-func (m *module) BandwidthStats() metrics.Stats {
+func (m *module) BandwidthStats(context.Context) metrics.Stats {
 	return m.bw.GetBandwidthTotals()
 }
 
-func (m *module) BandwidthForPeer(id peer.ID) metrics.Stats {
+func (m *module) BandwidthForPeer(_ context.Context, id peer.ID) metrics.Stats {
 	return m.bw.GetBandwidthForPeer(id)
 }
 
-func (m *module) BandwidthForProtocol(proto protocol.ID) metrics.Stats {
+func (m *module) BandwidthForProtocol(_ context.Context, proto protocol.ID) metrics.Stats {
 	return m.bw.GetBandwidthForProtocol(proto)
 }
 
-func (m *module) ResourceState() (rcmgr.ResourceManagerStat, error) {
+func (m *module) ResourceState(context.Context) (rcmgr.ResourceManagerStat, error) {
 	rms, ok := m.rm.(rcmgr.ResourceManagerState)
 	if !ok {
 		return rcmgr.ResourceManagerStat{}, fmt.Errorf("network.ResourceManager does not implement " +
@@ -179,7 +182,7 @@ func (m *module) ResourceState() (rcmgr.ResourceManagerStat, error) {
 	return rms.Stat(), nil
 }
 
-func (m *module) PubSubPeers(topic string) []peer.ID {
+func (m *module) PubSubPeers(_ context.Context, topic string) []peer.ID {
 	return m.ps.ListPeers(topic)
 }
 
@@ -188,22 +191,96 @@ func (m *module) PubSubPeers(topic string) []peer.ID {
 //
 //nolint:dupl
 type API struct {
-	Info                 func() peer.AddrInfo
-	Peers                func() []peer.ID
-	PeerInfo             func(id peer.ID) peer.AddrInfo
-	Connect              func(ctx context.Context, pi peer.AddrInfo) error
-	ClosePeer            func(id peer.ID) error
-	Connectedness        func(id peer.ID) network.Connectedness
-	NATStatus            func() (network.Reachability, error)
-	BlockPeer            func(p peer.ID) error
-	UnblockPeer          func(p peer.ID) error
-	ListBlockedPeers     func() []peer.ID
-	Protect              func(id peer.ID, tag string)
-	Unprotect            func(id peer.ID, tag string) bool
-	IsProtected          func(id peer.ID, tag string) bool
-	BandwidthStats       func() metrics.Stats
-	BandwidthForPeer     func(id peer.ID) metrics.Stats
-	BandwidthForProtocol func(proto protocol.ID) metrics.Stats
-	ResourceState        func() (rcmgr.ResourceManagerStat, error)
-	PubSubPeers          func(topic string) []peer.ID
+	Internal struct {
+		Info                 func(context.Context) peer.AddrInfo                         `perm:"admin"`
+		Peers                func(context.Context) []peer.ID                             `perm:"admin"`
+		PeerInfo             func(ctx context.Context, id peer.ID) peer.AddrInfo         `perm:"admin"`
+		Connect              func(ctx context.Context, pi peer.AddrInfo) error           `perm:"admin"`
+		ClosePeer            func(ctx context.Context, id peer.ID) error                 `perm:"admin"`
+		Connectedness        func(ctx context.Context, id peer.ID) network.Connectedness `perm:"admin"`
+		NATStatus            func(context.Context) (network.Reachability, error)         `perm:"admin"`
+		BlockPeer            func(ctx context.Context, p peer.ID) error                  `perm:"admin"`
+		UnblockPeer          func(ctx context.Context, p peer.ID) error                  `perm:"admin"`
+		ListBlockedPeers     func(context.Context) []peer.ID                             `perm:"admin"`
+		Protect              func(ctx context.Context, id peer.ID, tag string)           `perm:"admin"`
+		Unprotect            func(ctx context.Context, id peer.ID, tag string) bool      `perm:"admin"`
+		IsProtected          func(ctx context.Context, id peer.ID, tag string) bool      `perm:"admin"`
+		BandwidthStats       func(context.Context) metrics.Stats                         `perm:"admin"`
+		BandwidthForPeer     func(ctx context.Context, id peer.ID) metrics.Stats         `perm:"admin"`
+		BandwidthForProtocol func(ctx context.Context, proto protocol.ID) metrics.Stats  `perm:"admin"`
+		ResourceState        func(context.Context) (rcmgr.ResourceManagerStat, error)    `perm:"admin"`
+		PubSubPeers          func(ctx context.Context, topic string) []peer.ID           `perm:"admin"`
+	}
+}
+
+func (api *API) Info(ctx context.Context) peer.AddrInfo {
+	return api.Internal.Info(ctx)
+}
+
+func (api *API) Peers(ctx context.Context) []peer.ID {
+	return api.Internal.Peers(ctx)
+}
+
+func (api *API) PeerInfo(ctx context.Context, id peer.ID) peer.AddrInfo {
+	return api.Internal.PeerInfo(ctx, id)
+}
+
+func (api *API) Connect(ctx context.Context, pi peer.AddrInfo) error {
+	return api.Internal.Connect(ctx, pi)
+}
+
+func (api *API) ClosePeer(ctx context.Context, id peer.ID) error {
+	return api.Internal.ClosePeer(ctx, id)
+}
+
+func (api *API) Connectedness(ctx context.Context, id peer.ID) network.Connectedness {
+	return api.Internal.Connectedness(ctx, id)
+}
+
+func (api *API) NATStatus(ctx context.Context) (network.Reachability, error) {
+	return api.Internal.NATStatus(ctx)
+}
+
+func (api *API) BlockPeer(ctx context.Context, p peer.ID) error {
+	return api.Internal.BlockPeer(ctx, p)
+}
+
+func (api *API) UnblockPeer(ctx context.Context, p peer.ID) error {
+	return api.Internal.UnblockPeer(ctx, p)
+}
+
+func (api *API) ListBlockedPeers(ctx context.Context) []peer.ID {
+	return api.Internal.ListBlockedPeers(ctx)
+}
+
+func (api *API) Protect(ctx context.Context, id peer.ID, tag string) {
+	api.Internal.Protect(ctx, id, tag)
+}
+
+func (api *API) Unprotect(ctx context.Context, id peer.ID, tag string) bool {
+	return api.Internal.Unprotect(ctx, id, tag)
+}
+
+func (api *API) IsProtected(ctx context.Context, id peer.ID, tag string) bool {
+	return api.Internal.IsProtected(ctx, id, tag)
+}
+
+func (api *API) BandwidthStats(ctx context.Context) metrics.Stats {
+	return api.Internal.BandwidthStats(ctx)
+}
+
+func (api *API) BandwidthForPeer(ctx context.Context, id peer.ID) metrics.Stats {
+	return api.Internal.BandwidthForPeer(ctx, id)
+}
+
+func (api *API) BandwidthForProtocol(ctx context.Context, proto protocol.ID) metrics.Stats {
+	return api.Internal.BandwidthForProtocol(ctx, proto)
+}
+
+func (api *API) ResourceState(ctx context.Context) (rcmgr.ResourceManagerStat, error) {
+	return api.Internal.ResourceState(ctx)
+}
+
+func (api *API) PubSubPeers(ctx context.Context, topic string) []peer.ID {
+	return api.Internal.PubSubPeers(ctx, topic)
 }
diff --git a/nodebuilder/rpc/constructors.go b/nodebuilder/rpc/constructors.go
index 7b46e39c..40408f35 100644
--- a/nodebuilder/rpc/constructors.go
+++ b/nodebuilder/rpc/constructors.go
@@ -12,20 +12,20 @@ import (
 
 // RegisterEndpoints registers the given services on the rpc.
 func RegisterEndpoints(
-	state state.Module,
-	share share.Module,
-	fraud fraud.Module,
-	header header.Module,
-	daser das.Module,
-	p2p p2p.Module,
+	stateMod state.Module,
+	shareMod share.Module,
+	fraudMod fraud.Module,
+	headerMod header.Module,
+	daserMod das.Module,
+	p2pMod p2p.Module,
 	serv *rpc.Server,
 ) {
-	serv.RegisterService("state", state)
-	serv.RegisterService("share", share)
-	serv.RegisterService("fraud", fraud)
-	serv.RegisterService("header", header)
-	serv.RegisterService("das", daser)
-	serv.RegisterService("p2p", p2p)
+	serv.RegisterAuthedService("fraud", fraudMod, &fraud.API{})
+	serv.RegisterAuthedService("das", daserMod, &das.API{})
+	serv.RegisterAuthedService("header", headerMod, &header.API{})
+	serv.RegisterAuthedService("state", stateMod, &state.API{})
+	serv.RegisterAuthedService("share", shareMod, &share.API{})
+	serv.RegisterAuthedService("p2p", p2pMod, &p2p.API{})
 }
 
 func Server(cfg *Config) *rpc.Server {
diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go
index a8829539..bfa69617 100644
--- a/nodebuilder/share/module.go
+++ b/nodebuilder/share/module.go
@@ -3,13 +3,14 @@ package share
 import (
 	"context"
 
-	"github.com/celestiaorg/celestia-node/share"
-	"github.com/celestiaorg/celestia-node/share/availability/full"
-	"github.com/celestiaorg/celestia-node/share/availability/light"
-
+	"github.com/ipfs/go-datastore"
 	"go.uber.org/fx"
 
 	"github.com/celestiaorg/celestia-node/nodebuilder/node"
+	"github.com/celestiaorg/celestia-node/share"
+	"github.com/celestiaorg/celestia-node/share/availability/full"
+	"github.com/celestiaorg/celestia-node/share/availability/light"
+	"github.com/celestiaorg/celestia-node/share/eds"
 )
 
 func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option {
@@ -47,6 +48,17 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option
 		return fx.Module(
 			"share",
 			baseComponents,
+			fx.Provide(fx.Annotate(
+				func(path node.StorePath, ds datastore.Batching) (*eds.Store, error) {
+					return eds.NewStore(string(path), ds)
+				},
+				fx.OnStart(func(ctx context.Context, eds *eds.Store) error {
+					return eds.Start(ctx)
+				}),
+				fx.OnStop(func(ctx context.Context, eds *eds.Store) error {
+					return eds.Stop(ctx)
+				}),
+			)),
 			fx.Provide(fx.Annotate(
 				full.NewShareAvailability,
 				fx.OnStart(func(ctx context.Context, avail *full.ShareAvailability) error {
diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go
index 9c51ac5f..eaf049ae 100644
--- a/nodebuilder/share/share.go
+++ b/nodebuilder/share/share.go
@@ -7,6 +7,8 @@ import (
 	"github.com/celestiaorg/nmt/namespace"
 )
 
+var _ Module = (*API)(nil)
+
 // Module provides access to any data square or block share on the network.
 //
 // All Get methods provided on Module follow the following flow:
@@ -24,23 +26,62 @@ import (
 //
 //go:generate mockgen -destination=mocks/api.go -package=mocks . Module
 type Module interface {
-	// SharesAvailable subjectively validates if Shares committed to the given Root are available on the Network.
+	// SharesAvailable subjectively validates if Shares committed to the given Root are available on
+	// the Network.
 	SharesAvailable(context.Context, *share.Root) error
 	// ProbabilityOfAvailability calculates the probability of the data square
 	// being available based on the number of samples collected.
-	ProbabilityOfAvailability() float64
+	ProbabilityOfAvailability(context.Context) float64
 	GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error)
 	GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error)
-	// GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the given namespace.ID.
+	// GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the
+	// given namespace.ID.
 	GetSharesByNamespace(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error)
 }
 
 // API is a wrapper around Module for the RPC.
 // TODO(@distractedm1nd): These structs need to be autogenerated.
 type API struct {
-	SharesAvailable           func(context.Context, *share.Root) error
-	ProbabilityOfAvailability func() float64
-	GetShare                  func(ctx context.Context, dah *share.Root, row, col int) (share.Share, error)
-	GetShares                 func(ctx context.Context, root *share.Root) ([][]share.Share, error)
-	GetSharesByNamespace      func(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error)
+	Internal struct {
+		SharesAvailable           func(context.Context, *share.Root) error `perm:"public"`
+		ProbabilityOfAvailability func(context.Context) float64            `perm:"public"`
+		GetShare                  func(
+			ctx context.Context,
+			dah *share.Root,
+			row, col int,
+		) (share.Share, error) `perm:"public"`
+		GetShares func(
+			ctx context.Context,
+			root *share.Root,
+		) ([][]share.Share, error) `perm:"public"`
+		GetSharesByNamespace func(
+			ctx context.Context,
+			root *share.Root,
+			namespace namespace.ID,
+		) ([]share.Share, error) `perm:"public"`
+	}
+}
+
+func (api *API) SharesAvailable(ctx context.Context, root *share.Root) error {
+	return api.Internal.SharesAvailable(ctx, root)
+}
+
+func (api *API) ProbabilityOfAvailability(ctx context.Context) float64 {
+	return api.Internal.ProbabilityOfAvailability(ctx)
+}
+
+func (api *API) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) {
+	return api.Internal.GetShare(ctx, dah, row, col)
+}
+
+func (api *API) GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error) {
+	return api.Internal.GetShares(ctx, root)
+}
+
+func (api *API) GetSharesByNamespace(
+	ctx context.Context,
+	root *share.Root,
+	namespace namespace.ID,
+) ([]share.Share, error) {
+	return api.Internal.GetSharesByNamespace(ctx, root, namespace)
 }
diff --git a/nodebuilder/state/state.go b/nodebuilder/state/state.go
index 0b2a95f2..6058f5ec 100644
--- a/nodebuilder/state/state.go
+++ b/nodebuilder/state/state.go
@@ -11,6 +11,8 @@ import (
 	"github.com/celestiaorg/nmt/namespace"
 )
 
+var _ Module = (*API)(nil)
+
 // Module represents the behaviors necessary for a user to
 // query for state-related information and submit transactions/
 // messages to the Celestia network.
@@ -18,7 +20,7 @@ import (
 //go:generate mockgen -destination=mocks/api.go -package=mocks . Module
 type Module interface {
 	// IsStopped checks if the Module's context has been stopped
-	IsStopped() bool
+	IsStopped(ctx context.Context) bool
 
 	// AccountAddress retrieves the address of the node's account/signer
 	AccountAddress(ctx context.Context) (state.Address, error)
@@ -79,47 +81,154 @@ type Module interface {
 // API is a wrapper around Module for the RPC.
 // TODO(@distractedm1nd): These structs need to be autogenerated.
 type API struct {
-	IsStopped         func() bool
-	Balance           func(ctx context.Context) (*state.Balance, error)
-	BalanceForAddress func(ctx context.Context, addr state.Address) (*state.Balance, error)
-	Transfer          func(
-		ctx context.Context,
-		to state.AccAddress,
-		amount math.Int,
-		gasLimit uint64,
-	) (*state.TxResponse, error)
-	SubmitTx         func(ctx context.Context, tx state.Tx) (*state.TxResponse, error)
-	SubmitPayForData func(ctx context.Context, nID namespace.ID, data []byte, gasLim uint64) (
-		*state.TxResponse,
-		error,
-	)
-	CancelUnbondingDelegation func(
-		ctx context.Context,
-		valAddr state.ValAddress,
-		amount,
-		height state.Int,
-		gasLim uint64,
-	) (*state.TxResponse, error)
-	BeginRedelegate func(
-		ctx context.Context,
-		srcValAddr,
-		dstValAddr state.ValAddress,
-		amount state.Int,
-		gasLim uint64,
-	) (*state.TxResponse, error)
-	Undelegate func(ctx context.Context, delAddr state.ValAddress, amount state.Int, gasLim uint64) (
-		*state.TxResponse,
-		error,
-	)
-	Delegate func(ctx context.Context, delAddr state.ValAddress, amount state.Int, gasLim uint64) (
-		*state.TxResponse,
-		error,
-	)
-	QueryDelegation    func(ctx context.Context, valAddr state.ValAddress) (*types.QueryDelegationResponse, error)
-	QueryUnbonding     func(ctx context.Context, valAddr state.ValAddress) (*types.QueryUnbondingDelegationResponse, error)
-	QueryRedelegations func(
-		ctx context.Context,
-		srcValAddr,
-		dstValAddr state.ValAddress,
-	) (*types.QueryRedelegationsResponse, error)
+	Internal struct {
+		AccountAddress    func(ctx context.Context) (state.Address, error)                      `perm:"read"`
+		IsStopped         func(ctx context.Context) bool                                        `perm:"public"`
+		Balance           func(ctx context.Context) (*state.Balance, error)                     `perm:"read"`
+		BalanceForAddress func(ctx context.Context, addr state.Address) (*state.Balance, error) `perm:"public"`
+		Transfer          func(
+			ctx context.Context,
+			to state.AccAddress,
+			amount math.Int,
+			gasLimit uint64,
+		) (*state.TxResponse, error) `perm:"write"`
+		SubmitTx         func(ctx context.Context, tx state.Tx) (*state.TxResponse, error) `perm:"write"`
+		SubmitPayForData func(
+			ctx context.Context,
+			nID namespace.ID,
+			data []byte,
+			gasLim uint64,
+		) (*state.TxResponse, error) `perm:"write"`
+		CancelUnbondingDelegation func(
+			ctx context.Context,
+			valAddr state.ValAddress,
+			amount,
+			height state.Int,
+			gasLim uint64,
+		) (*state.TxResponse, error) `perm:"write"`
+		BeginRedelegate func(
+			ctx context.Context,
+			srcValAddr,
+			dstValAddr state.ValAddress,
+			amount state.Int,
+			gasLim uint64,
+		) (*state.TxResponse, error) `perm:"write"`
+		Undelegate func(
+			ctx context.Context,
+			delAddr state.ValAddress,
+			amount state.Int,
+			gasLim uint64,
+		) (*state.TxResponse, error) `perm:"write"`
+		Delegate func(
+			ctx context.Context,
+			delAddr state.ValAddress,
+			amount state.Int,
+			gasLim uint64,
+		) (*state.TxResponse, error) `perm:"write"`
+		QueryDelegation func(
+			ctx context.Context,
+			valAddr state.ValAddress,
+		) (*types.QueryDelegationResponse, error) `perm:"public"`
+		QueryUnbonding func(
+			ctx context.Context,
+			valAddr state.ValAddress,
+		) (*types.QueryUnbondingDelegationResponse, error) `perm:"public"`
+		QueryRedelegations func(
+			ctx context.Context,
+			srcValAddr,
+			dstValAddr state.ValAddress,
+		) (*types.QueryRedelegationsResponse, error) `perm:"public"`
+	}
+}
+
+func (api *API) AccountAddress(ctx context.Context) (state.Address, error) {
+	return api.Internal.AccountAddress(ctx)
+}
+
+func (api *API) IsStopped(ctx context.Context) bool {
+	return api.Internal.IsStopped(ctx)
+}
+
+func (api *API) BalanceForAddress(ctx context.Context, addr state.Address) (*state.Balance, error) {
+	return api.Internal.BalanceForAddress(ctx, addr)
+}
+
+func (api *API) Transfer(
+	ctx context.Context,
+	to state.AccAddress,
+	amount math.Int,
+	gasLimit uint64,
+) (*state.TxResponse, error) {
+	return api.Internal.Transfer(ctx, to, amount, gasLimit)
+}
+
+func (api *API) SubmitTx(ctx context.Context, tx state.Tx) (*state.TxResponse, error) {
+	return api.Internal.SubmitTx(ctx, tx)
+}
+
+func (api *API) SubmitPayForData(
+	ctx context.Context,
+	nID namespace.ID,
+	data []byte,
+	gasLim uint64,
+) (*state.TxResponse, error) {
+	return api.Internal.SubmitPayForData(ctx, nID, data, gasLim)
+}
+
+func (api *API) CancelUnbondingDelegation(
+	ctx context.Context,
+	valAddr state.ValAddress,
+	amount, height state.Int,
+	gasLim uint64,
+) (*state.TxResponse, error) {
+	return api.Internal.CancelUnbondingDelegation(ctx, valAddr, amount, height, gasLim)
+}
+
+func (api *API) BeginRedelegate(
+	ctx context.Context,
+	srcValAddr, dstValAddr state.ValAddress,
+	amount state.Int,
+	gasLim uint64,
+) (*state.TxResponse, error) {
+	return api.Internal.BeginRedelegate(ctx, srcValAddr, dstValAddr, amount, gasLim)
+}
+
+func (api *API) Undelegate(
+	ctx context.Context,
+	delAddr state.ValAddress,
+	amount state.Int,
+	gasLim uint64,
+) (*state.TxResponse, error) {
+	return api.Internal.Undelegate(ctx, delAddr, amount, gasLim)
+}
+
+func (api *API) Delegate(
+	ctx context.Context,
+	delAddr state.ValAddress,
+	amount state.Int,
+	gasLim uint64,
+) (*state.TxResponse, error) {
+	return api.Internal.Delegate(ctx, delAddr, amount, gasLim)
+}
+
+func (api *API) QueryDelegation(ctx context.Context, valAddr state.ValAddress) (*types.QueryDelegationResponse, error) {
+	return api.Internal.QueryDelegation(ctx, valAddr)
+}
+
+func (api *API) QueryUnbonding(
+	ctx context.Context,
+	valAddr state.ValAddress,
+) (*types.QueryUnbondingDelegationResponse, error) {
+	return api.Internal.QueryUnbonding(ctx, valAddr)
+}
+
+func (api *API) QueryRedelegations(
+	ctx context.Context,
+	srcValAddr, dstValAddr state.ValAddress,
+) (*types.QueryRedelegationsResponse, error) {
+	return api.Internal.QueryRedelegations(ctx, srcValAddr, dstValAddr)
+}
+
+func (api *API) Balance(ctx context.Context) (*state.Balance, error) {
+	return api.Internal.Balance(ctx)
 }
diff --git a/share/availability.go b/share/availability.go
index 7bd52109..712bf974 100644
--- a/share/availability.go
+++ b/share/availability.go
@@ -28,5 +28,5 @@ type Availability interface {
 	// ProbabilityOfAvailability calculates the probability of the data square
 	// being available based on the number of samples collected.
 	// TODO(@Wondertan): Merge with SharesAvailable method, eventually
-	ProbabilityOfAvailability() float64
+	ProbabilityOfAvailability(context.Context) float64
 }
diff --git a/share/availability/cache/availability.go b/share/availability/cache/availability.go
index f8f3d876..e9ee88f8 100644
--- a/share/availability/cache/availability.go
+++ b/share/availability/cache/availability.go
@@ -81,8 +81,8 @@ func (ca *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro
 	return err
 }
 
-func (ca *ShareAvailability) ProbabilityOfAvailability() float64 {
-	return ca.avail.ProbabilityOfAvailability()
+func (ca *ShareAvailability) ProbabilityOfAvailability(ctx context.Context) float64 {
+	return ca.avail.ProbabilityOfAvailability(ctx)
 }
 
 // Close flushes all queued writes to disk.
diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go
index 1a5c5760..68ebb564 100644
--- a/share/availability/full/availability.go
+++ b/share/availability/full/availability.go
@@ -72,6 +72,6 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro
 	return err
 }
 
-func (fa *ShareAvailability) ProbabilityOfAvailability() float64 {
+func (fa *ShareAvailability) ProbabilityOfAvailability(context.Context) float64 {
 	return 1
 }
diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go
index ac8c805d..71247417 100644
--- a/share/availability/light/availability.go
+++ b/share/availability/light/availability.go
@@ -122,6 +122,6 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo
 // (DefaultSampleAmount).
 //
 // Formula: 1 - (0.75 ** amount of samples)
-func (la *ShareAvailability) ProbabilityOfAvailability() float64 {
+func (la *ShareAvailability) ProbabilityOfAvailability(context.Context) float64 {
 	return 1 - math.Pow(0.75, float64(DefaultSampleAmount))
 }
diff --git a/share/eds/accessor_cache.go b/share/eds/accessor_cache.go
new file mode 100644
index 00000000..047ce491
--- /dev/null
+++ b/share/eds/accessor_cache.go
@@ -0,0 +1,108 @@
+package eds
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"sync"
+
+	"github.com/filecoin-project/dagstore"
+	"github.com/filecoin-project/dagstore/shard"
+	lru "github.com/hashicorp/golang-lru"
+)
+
+var (
+	defaultCacheSize = 128
+	errCacheMiss     = errors.New("accessor not found in blockstore cache")
+)
+
+// accessorWithBlockstore is the value that we store in the blockstore cache
+type accessorWithBlockstore struct {
+	sa *dagstore.ShardAccessor
+	// blockstore is stored separately because each access to the blockstore over the shard accessor
+	// reopens the underlying CAR.
+	bs dagstore.ReadBlockstore
+}
+
+type blockstoreCache struct {
+	// stripedLocks prevents simultaneous RW access to the blockstore cache for a shard. Instead
+	// of using only one lock or one lock per key, we stripe the shard keys across 256 locks. 256 is
+	// chosen because it 0-255 is the range of values we get looking at the last byte of the key.
+	stripedLocks [256]sync.Mutex
+	// caches the blockstore for a given shard for shard read affinity i.e.
+	// further reads will likely be from the same shard. Maps (shard key -> blockstore).
+	cache *lru.Cache
+}
+
+func newBlockstoreCache(cacheSize int) (*blockstoreCache, error) {
+	// instantiate the blockstore cache
+	bslru, err := lru.NewWithEvict(cacheSize, func(_ interface{}, val interface{}) {
+		// ensure we close the blockstore for a shard when it's evicted so dagstore can gc it.
+		abs, ok := val.(*accessorWithBlockstore)
+		if !ok {
+			panic(fmt.Sprintf(
+				"casting value from cache to accessorWithBlockstore: %s",
+				reflect.TypeOf(val),
+			))
+		}
+
+		if err := abs.sa.Close(); err != nil {
+			log.Errorf("couldn't close accessor after cache eviction: %s", err)
+		}
+	})
+	if err != nil {
+		return nil, fmt.Errorf("failed to instantiate blockstore cache: %w", err)
+	}
+	return &blockstoreCache{cache: bslru}, nil
+}
+
+// Get retrieves the blockstore for a given shard key from the cache. If the blockstore is not in
+// the cache, it returns an errCacheMiss
+func (bc *blockstoreCache) Get(shardContainingCid shard.Key) (*accessorWithBlockstore, error) {
+	lk := &bc.stripedLocks[shardKeyToStriped(shardContainingCid)]
+	lk.Lock()
+	defer lk.Unlock()
+
+	// We've already ensured that the given shard has the cid/multihash we are looking for.
+	val, ok := bc.cache.Get(shardContainingCid)
+	if !ok {
+		return nil, errCacheMiss
+	}
+
+	accessor, ok := val.(*accessorWithBlockstore)
+	if !ok {
+		panic(fmt.Sprintf(
+			"casting value from cache to accessorWithBlockstore: %s",
+			reflect.TypeOf(val),
+		))
+	}
+	return accessor, nil
+}
+
+// Add adds a blockstore for a given shard key to the cache.
+func (bc *blockstoreCache) Add(
+	shardContainingCid shard.Key,
+	accessor *dagstore.ShardAccessor,
+) (*accessorWithBlockstore, error) {
+	blockStore, err := accessor.Blockstore()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get blockstore from accessor: %w", err)
+	}
+
+	lk := &bc.stripedLocks[shardKeyToStriped(shardContainingCid)]
+	lk.Lock()
+	defer lk.Unlock()
+
+	newAccessor := &accessorWithBlockstore{
+		bs: blockStore,
+		sa: accessor,
+	}
+	bc.cache.Add(shardContainingCid, newAccessor)
+	return newAccessor, nil
+}
+
+// shardKeyToStriped returns the index of the lock to use for a given shard key. We use the last
+// byte of the shard key as the pseudo-random index.
+func shardKeyToStriped(sk shard.Key) byte {
+	return sk.String()[len(sk.String())-1]
+}
diff --git a/share/eds/blockstore.go b/share/eds/blockstore.go
new file mode 100644
index 00000000..a377fd53
--- /dev/null
+++ b/share/eds/blockstore.go
@@ -0,0 +1,119 @@
+package eds
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"github.com/filecoin-project/dagstore"
+	blocks "github.com/ipfs/go-block-format"
+	"github.com/ipfs/go-cid"
+	bstore "github.com/ipfs/go-ipfs-blockstore"
+	ipld "github.com/ipfs/go-ipld-format"
+)
+
+var _ bstore.Blockstore = (*blockstore)(nil)
+
+var (
+	errUnsupportedOperation = errors.New("unsupported operation")
+	errShardNotFound        = errors.New("the provided cid does not map to any shard")
+)
+
+// blockstore implements the store.Blockstore interface on an EDSStore.
+// The lru cache approach is heavily inspired by the existing implementation upstream.
+// We simplified the design to not support multiple shards per key, call GetSize directly on the
+// underlying RO blockstore, and do not throw errors on Put/PutMany. Also, we do not abstract away
+// the blockstore operations.
+//
+// The intuition here is that each CAR file is its own blockstore, so we need this top level
+// implementation to allow for the blockstore operations to be routed to the underlying stores.
+type blockstore struct {
+	store *Store
+	cache *blockstoreCache
+}
+
+func newBlockstore(store *Store, cache *blockstoreCache) *blockstore {
+	return &blockstore{
+		store: store,
+		cache: cache,
+	}
+}
+
+func (bs *blockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) {
+	keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash())
+	if err != nil {
+		return false, fmt.Errorf("failed to find shards containing multihash: %w", err)
+	}
+	return len(keys) > 0, nil
+}
+
+func (bs *blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) {
+	blockstr, err := bs.getReadOnlyBlockstore(ctx, cid)
+	if err != nil {
+		log.Debugf("failed to get blockstore for cid %s: %s", cid, err)
+		// nmt's GetNode expects an ipld.ErrNotFound when a cid is not found.
+		return nil, ipld.ErrNotFound{Cid: cid}
+	}
+	return blockstr.Get(ctx, cid)
+}
+
+func (bs *blockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) {
+	blockstr, err := bs.getReadOnlyBlockstore(ctx, cid)
+	if err != nil {
+		return 0, err
+	}
+	return blockstr.GetSize(ctx, cid)
+}
+
+// DeleteBlock is a noop on the EDS blockstore that returns an errUnsupportedOperation when called.
+func (bs *blockstore) DeleteBlock(context.Context, cid.Cid) error {
+	return errUnsupportedOperation
+}
+
+// Put is a noop on the EDS blockstore, but it does not return an error because it is called by
+// bitswap. For clarification, an implementation of Put does not make sense in this context because
+// it is unclear which CAR file the block should be written to.
+//
+// TODO: throw errUnsupportedOperation after issue #1440
+func (bs *blockstore) Put(context.Context, blocks.Block) error {
+	return nil
+}
+
+// PutMany is a noop on the EDS blockstore, but it does not return an error because it is called by
+// bitswap. For clarification, an implementation of PutMany does not make sense in this context
+// because it is unclear which CAR file the blocks should be written to.
+//
+// TODO: throw errUnsupportedOperation after issue #1440
+func (bs *blockstore) PutMany(context.Context, []blocks.Block) error {
+	return nil
+}
+
+// AllKeysChan is a noop on the EDS blockstore because the keys are not stored in a single CAR file.
+func (bs *blockstore) AllKeysChan(context.Context) (<-chan cid.Cid, error) {
+	return nil, errUnsupportedOperation
+}
+
+// HashOnRead is a noop on the EDS blockstore but an error cannot be returned due to the method
+// signature from the blockstore interface.
+func (bs *blockstore) HashOnRead(bool) {
+	log.Warnf("HashOnRead is a noop on the EDS blockstore")
+}
+
+// getReadOnlyBlockstore finds the underlying blockstore of the shard that contains the given CID.
+func (bs *blockstore) getReadOnlyBlockstore(ctx context.Context, cid cid.Cid) (dagstore.ReadBlockstore, error) {
+	keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash())
+	if err != nil {
+		return nil, fmt.Errorf("failed to find shards containing multihash: %w", err)
+	}
+	if len(keys) == 0 {
+		return nil, errShardNotFound
+	}
+
+	// a share can exist in multiple EDSes, so just take the first one.
+	shardKey := keys[0]
+	accessor, err := bs.store.getAccessor(ctx, shardKey)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get accessor for shard %s: %w", shardKey, err)
+	}
+	return accessor.bs, nil
+}
diff --git a/share/eds/eds.go b/share/eds/eds.go
index 5824ae11..236d5f65 100644
--- a/share/eds/eds.go
+++ b/share/eds/eds.go
@@ -12,7 +12,7 @@ import (
 	"github.com/ipfs/go-cid"
 	ds "github.com/ipfs/go-datastore"
 	dssync "github.com/ipfs/go-datastore/sync"
-	blockstore "github.com/ipfs/go-ipfs-blockstore"
+	bstore "github.com/ipfs/go-ipfs-blockstore"
 	format "github.com/ipfs/go-ipld-format"
 	"github.com/ipld/go-car"
 	"github.com/ipld/go-car/util"
@@ -21,10 +21,11 @@ import (
 	"github.com/celestiaorg/celestia-app/pkg/da"
 	"github.com/celestiaorg/celestia-app/pkg/wrapper"
 
-	"github.com/celestiaorg/celestia-node/share"
-	"github.com/celestiaorg/celestia-node/share/ipld"
 	"github.com/celestiaorg/nmt"
 	"github.com/celestiaorg/rsmt2d"
+
+	"github.com/celestiaorg/celestia-node/share"
+	"github.com/celestiaorg/celestia-node/share/ipld"
 )
 
 var ErrEmptySquare = errors.New("share: importing empty data")
@@ -35,7 +36,7 @@ type writingSession struct {
 	eds *rsmt2d.ExtendedDataSquare
 	// store is an in-memory blockstore, used to cache the inner nodes (proofs) while we walk the nmt
 	// tree.
-	store blockstore.Blockstore
+	store bstore.Blockstore
 	w     io.Writer
 }
 
@@ -63,7 +64,7 @@ func WriteEDS(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer)
 		return fmt.Errorf("share: writing shares: %w", err)
 	}
 
-	// 4. Iterates over in-memory Blockstore and writes proofs to the CAR
+	// 4. Iterates over in-memory blockstore and writes proofs to the CAR
 	err = writer.writeProofs(ctx)
 	if err != nil {
 		return fmt.Errorf("share: writing proofs: %w", err)
@@ -74,7 +75,7 @@ func WriteEDS(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer)
 // initializeWriter reimports the EDS into an in-memory blockstore in order to cache the proofs.
 func initializeWriter(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer) (*writingSession, error) {
 	// we use an in-memory blockstore and an offline exchange
-	store := blockstore.NewBlockstore(dssync.MutexWrap(ds.NewMapDatastore()))
+	store := bstore.NewBlockstore(dssync.MutexWrap(ds.NewMapDatastore()))
 	bs := blockservice.New(store, nil)
 	// shares are extracted from the eds so that we can reimport them to traverse
 	shares := share.ExtractEDS(eds)
@@ -230,13 +231,14 @@ func rootsToCids(eds *rsmt2d.ExtendedDataSquare) ([]cid.Cid, error) {
 // Only the first quadrant will be read, which represents the original data.
 // The returned EDS is guaranteed to be full and valid against the DataRoot, otherwise ReadEDS
 // errors.
-func ReadEDS(ctx context.Context, r io.Reader, root share.Root) (*rsmt2d.ExtendedDataSquare, error) {
+func ReadEDS(ctx context.Context, r io.Reader, root share.DataHash) (*rsmt2d.ExtendedDataSquare, error) {
 	carReader, err := car.NewCarReader(r)
 	if err != nil {
 		return nil, fmt.Errorf("share: reading car file: %w", err)
 	}
 
-	odsWidth := len(root.RowsRoots) / 2
+	// car header includes both row and col roots in header
+	odsWidth := len(carReader.Header.Roots) / 4
 	odsSquareSize := odsWidth * odsWidth
 	shares := make([][]byte, odsSquareSize)
 	// the first quadrant is stored directly after the header,
@@ -262,11 +264,11 @@ func ReadEDS(ctx context.Context, r io.Reader, root share.Root) (*rsmt2d.Extende
 	}
 
 	newDah := da.NewDataAvailabilityHeader(eds)
-	if !bytes.Equal(newDah.Hash(), root.Hash()) {
+	if !bytes.Equal(newDah.Hash(), root) {
 		return nil, fmt.Errorf(
 			"share: content integrity mismatch: imported root %s doesn't match expected root %s",
 			newDah.Hash(),
-			root.Hash(),
+			root,
 		)
 	}
 	return eds, nil
diff --git a/share/eds/store.go b/share/eds/store.go
index ddfef04b..88179d16 100644
--- a/share/eds/store.go
+++ b/share/eds/store.go
@@ -5,36 +5,49 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"sync/atomic"
+	"time"
 
 	"github.com/filecoin-project/dagstore"
 	"github.com/filecoin-project/dagstore/index"
 	"github.com/filecoin-project/dagstore/mount"
 	"github.com/filecoin-project/dagstore/shard"
 	"github.com/ipfs/go-datastore"
-
-	"github.com/celestiaorg/celestia-node/share"
+	bstore "github.com/ipfs/go-ipfs-blockstore"
 
 	"github.com/celestiaorg/rsmt2d"
+
+	"github.com/celestiaorg/celestia-node/share"
 )
 
 const (
 	blocksPath     = "/blocks/"
 	indexPath      = "/index/"
 	transientsPath = "/transients/"
+
+	defaultGCInterval = time.Hour
 )
 
 // Store maintains (via DAGStore) a top-level index enabling granular and efficient random access to
 // every share and/or Merkle proof over every registered CARv1 file. The EDSStore provides a custom
-// Blockstore interface implementation to achieve access. The main use-case is randomized sampling
+// blockstore interface implementation to achieve access. The main use-case is randomized sampling
 // over the whole chain of EDS block data and getting data by namespace.
 type Store struct {
+	cancel context.CancelFunc
+
 	dgstr  *dagstore.DAGStore
 	mounts *mount.Registry
 
+	cache *blockstoreCache
+	bs    bstore.Blockstore
+
 	topIdx index.Inverted
 	carIdx index.FullIndexRepo
 
-	basepath string
+	basepath   string
+	gcInterval time.Duration
+	// lastGCResult is only stored on the store for testing purposes.
+	lastGCResult atomic.Pointer[dagstore.GCResult]
 }
 
 // NewStore creates a new EDS Store under the given basepath and datastore.
@@ -69,31 +82,67 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) {
 		return nil, fmt.Errorf("failed to create DAGStore: %w", err)
 	}
 
-	return &Store{
-		basepath: basepath,
-		dgstr:    dagStore,
-		topIdx:   invertedRepo,
-		carIdx:   fsRepo,
-		mounts:   r,
-	}, nil
+	cache, err := newBlockstoreCache(defaultCacheSize)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create blockstore cache: %w", err)
+	}
+
+	store := &Store{
+		basepath:   basepath,
+		dgstr:      dagStore,
+		topIdx:     invertedRepo,
+		carIdx:     fsRepo,
+		gcInterval: defaultGCInterval,
+		mounts:     r,
+		cache:      cache,
+	}
+	store.bs = newBlockstore(store, cache)
+	return store, nil
 }
 
-// Start starts the underlying DAGStore.
-func (s *Store) Start(ctx context.Context) error {
+func (s *Store) Start(context.Context) error {
+	ctx, cancel := context.WithCancel(context.Background())
+	s.cancel = cancel
+
+	go s.gc(ctx)
 	return s.dgstr.Start(ctx)
 }
 
 // Stop stops the underlying DAGStore.
-func (s *Store) Stop() error {
+func (s *Store) Stop(context.Context) error {
+	defer s.cancel()
 	return s.dgstr.Close()
 }
 
+// gc periodically removes all inactive or errored shards.
+func (s *Store) gc(ctx context.Context) {
+	ticker := time.NewTicker(s.gcInterval)
+	// initialize empty gc result to avoid panic on access
+	s.lastGCResult.Store(&dagstore.GCResult{
+		Shards: make(map[shard.Key]error),
+	})
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-ticker.C:
+			res, err := s.dgstr.GC(ctx)
+			if err != nil {
+				log.Errorf("garbage collecting dagstore: %v", err)
+				return
+			}
+			s.lastGCResult.Store(res)
+		}
+
+	}
+}
+
 // Put stores the given data square with DataRoot's hash as a key.
 //
 // The square is verified on the Exchange level, and Put only stores the square, trusting it.
 // The resulting file stores all the shares and NMT Merkle Proofs of the EDS.
 // Additionally, the file gets indexed s.t. store.Blockstore can access them.
-func (s *Store) Put(ctx context.Context, root share.Root, square *rsmt2d.ExtendedDataSquare) error {
+func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.ExtendedDataSquare) error {
 	key := root.String()
 	f, err := os.OpenFile(s.basepath+blocksPath+key, os.O_CREATE|os.O_WRONLY, 0600)
 	if err != nil {
@@ -131,28 +180,66 @@ func (s *Store) Put(ctx context.Context, root share.Root, square *rsmt2d.Extende
 // NMT Merkle proofs. Integrity of the store data is not verified.
 //
 // Caller must Close returned reader after reading.
-func (s *Store) GetCAR(ctx context.Context, root share.Root) (io.ReadCloser, error) {
+func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.ReadCloser, error) {
 	key := root.String()
+	accessor, err := s.getAccessor(ctx, shard.KeyFromString(key))
+	if err != nil {
+		return nil, fmt.Errorf("failed to get accessor: %w", err)
+	}
+	return accessor.sa, nil
+}
+
+// Blockstore returns an IPFS blockstore providing access to individual shares/nodes of all EDS
+// registered on the Store. NOTE: The blockstore does not store whole Celestia Blocks but IPFS
+// blocks. We represent `shares` and NMT Merkle proofs as IPFS blocks and IPLD nodes so Bitswap can
+// access those.
+func (s *Store) Blockstore() bstore.Blockstore {
+	return s.bs
+}
 
+// CARBlockstore returns the IPFS blockstore that provides access to the IPLD blocks stored in an
+// individual CAR file.
+func (s *Store) CARBlockstore(ctx context.Context, dataHash []byte) (dagstore.ReadBlockstore, error) {
+	key := shard.KeyFromString(fmt.Sprintf("%X", dataHash))
+	accessor, err := s.getAccessor(ctx, key)
+	if err != nil {
+		return nil, err
+	}
+
+	return accessor.bs, nil
+}
+
+func (s *Store) getAccessor(ctx context.Context, key shard.Key) (*accessorWithBlockstore, error) {
+	// try to fetch from cache
+	accessor, err := s.cache.Get(key)
+	if err != nil && err != errCacheMiss {
+		log.Errorw("unexpected error while reading key from bs cache %s: %s", key, err)
+	}
+	if accessor != nil {
+		return accessor, nil
+	}
+
+	// wasn't found in cache, so acquire it and add to cache
 	ch := make(chan dagstore.ShardResult, 1)
-	err := s.dgstr.AcquireShard(ctx, shard.KeyFromString(key), ch, dagstore.AcquireOpts{})
+	err = s.dgstr.AcquireShard(ctx, key, ch, dagstore.AcquireOpts{})
 	if err != nil {
-		return nil, fmt.Errorf("failed to initiate shard acquisition: %w", err)
+		return nil, fmt.Errorf("failed to initialize shard acquisition: %w", err)
 	}
 
 	select {
+	case res := <-ch:
+		if res.Error != nil {
+			return nil, fmt.Errorf("failed to acquire shard: %w", res.Error)
+		}
+		return s.cache.Add(key, res.Accessor)
 	case <-ctx.Done():
 		return nil, ctx.Err()
-	case result := <-ch:
-		if result.Error != nil {
-			return nil, fmt.Errorf("failed to acquire shard: %w", result.Error)
-		}
-		return result.Accessor, nil
 	}
 }
 
-// Remove removes EDS from Store by the given share.Root and cleans up all the indexing.
-func (s *Store) Remove(ctx context.Context, root share.Root) error {
+// Remove removes EDS from Store by the given share.Root hash and cleans up all
+// the indexing.
+func (s *Store) Remove(ctx context.Context, root share.DataHash) error {
 	key := root.String()
 
 	ch := make(chan dagstore.ShardResult, 1)
@@ -189,7 +276,7 @@ func (s *Store) Remove(ctx context.Context, root share.Root) error {
 //
 // It reads only one quadrant(1/4) of the EDS and verifies the integrity of the stored data by
 // recomputing it.
-func (s *Store) Get(ctx context.Context, root share.Root) (*rsmt2d.ExtendedDataSquare, error) {
+func (s *Store) Get(ctx context.Context, root share.DataHash) (*rsmt2d.ExtendedDataSquare, error) {
 	f, err := s.GetCAR(ctx, root)
 	if err != nil {
 		return nil, fmt.Errorf("failed to get CAR file: %w", err)
@@ -201,8 +288,8 @@ func (s *Store) Get(ctx context.Context, root share.Root) (*rsmt2d.ExtendedDataS
 	return eds, nil
 }
 
-// Has checks if EDS exists by the given share.Root.
-func (s *Store) Has(ctx context.Context, root share.Root) (bool, error) {
+// Has checks if EDS exists by the given share.Root hash.
+func (s *Store) Has(ctx context.Context, root share.DataHash) (bool, error) {
 	key := root.String()
 	info, err := s.dgstr.GetShardInfo(shard.KeyFromString(key))
 	if err == dagstore.ErrShardUnknown {
@@ -213,16 +300,15 @@ func (s *Store) Has(ctx context.Context, root share.Root) (bool, error) {
 }
 
 func setupPath(basepath string) error {
-	perms := os.FileMode(0755)
-	err := os.Mkdir(basepath+blocksPath, perms)
+	err := os.MkdirAll(basepath+blocksPath, os.ModePerm)
 	if err != nil {
 		return fmt.Errorf("failed to create blocks directory: %w", err)
 	}
-	err = os.Mkdir(basepath+transientsPath, perms)
+	err = os.MkdirAll(basepath+transientsPath, os.ModePerm)
 	if err != nil {
 		return fmt.Errorf("failed to create transients directory: %w", err)
 	}
-	err = os.Mkdir(basepath+indexPath, perms)
+	err = os.MkdirAll(basepath+indexPath, os.ModePerm)
 	if err != nil {
 		return fmt.Errorf("failed to create index directory: %w", err)
 	}
diff --git a/share/share.go b/share/share.go
index 8923a65d..8fbf427a 100644
--- a/share/share.go
+++ b/share/share.go
@@ -1,6 +1,8 @@
 package share
 
 import (
+	"fmt"
+
 	"go.opentelemetry.io/otel"
 
 	"github.com/celestiaorg/celestia-app/pkg/appconsts"
@@ -39,3 +41,17 @@ func ID(s Share) namespace.ID {
 func Data(s Share) []byte {
 	return s[NamespaceSize:]
 }
+
+// DataHash is a representation of the Root hash.
+type DataHash []byte
+
+func (dh DataHash) Validate() error {
+	if len(dh) != 32 {
+		return fmt.Errorf("invalid hash size, expected 32, got %d", len(dh))
+	}
+	return nil
+}
+
+func (dh DataHash) String() string {
+	return fmt.Sprintf("%X", []byte(dh))
+}
diff --git a/state/core_access.go b/state/core_access.go
index c4b38443..ee670e48 100644
--- a/state/core_access.go
+++ b/state/core_access.go
@@ -413,6 +413,6 @@ func (ca *CoreAccessor) QueryRedelegations(
 	})
 }
 
-func (ca *CoreAccessor) IsStopped() bool {
+func (ca *CoreAccessor) IsStopped(context.Context) bool {
 	return ca.ctx.Err() != nil
 }


Test patch:
diff --git a/api/rpc_test.go b/api/rpc_test.go
index 80f3546c..b06fa3a3 100644
--- a/api/rpc_test.go
+++ b/api/rpc_test.go
@@ -19,6 +19,7 @@ import (
 	fraudMock "github.com/celestiaorg/celestia-node/nodebuilder/fraud/mocks"
 	headerMock "github.com/celestiaorg/celestia-node/nodebuilder/header/mocks"
 	"github.com/celestiaorg/celestia-node/nodebuilder/node"
+	p2pMock "github.com/celestiaorg/celestia-node/nodebuilder/p2p/mocks"
 	shareMock "github.com/celestiaorg/celestia-node/nodebuilder/share/mocks"
 	stateMock "github.com/celestiaorg/celestia-node/nodebuilder/state/mocks"
 	"github.com/celestiaorg/celestia-node/state"
@@ -62,13 +63,16 @@ func TestModulesImplementFullAPI(t *testing.T) {
 	client := reflect.TypeOf(new(client.Client)).Elem()
 	for i := 0; i < client.NumField(); i++ {
 		module := client.Field(i)
-		for j := 0; j < module.Type.NumField(); j++ {
-			impl := module.Type.Field(j)
+		// the "closers" field is not an actual module
+		if module.Name == "closer" {
+			continue
+		}
+		internal, ok := module.Type.FieldByName("Internal")
+		require.True(t, ok, "module %s's API does not have an Internal field", module.Name)
+		for j := 0; j < internal.Type.NumField(); j++ {
+			impl := internal.Type.Field(j)
 			method, _ := api.MethodByName(impl.Name)
-			// closers is the only thing on the Client struct that doesn't exist in the API
-			if impl.Name != "closers" {
-				require.Equal(t, method.Type, impl.Type, "method %s does not match", impl.Name)
-			}
+			require.Equal(t, method.Type, impl.Type, "method %s does not match", impl.Name)
 		}
 	}
 }
@@ -140,6 +144,7 @@ func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) {
 		fraudMock.NewMockModule(ctrl),
 		headerMock.NewMockModule(ctrl),
 		dasMock.NewMockModule(ctrl),
+		p2pMock.NewMockModule(ctrl),
 	}
 
 	// given the behavior of fx.Invoke, this invoke will be called last as it is added at the root
@@ -150,6 +155,7 @@ func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) {
 		srv.RegisterService("fraud", mockAPI.Fraud)
 		srv.RegisterService("header", mockAPI.Header)
 		srv.RegisterService("das", mockAPI.Das)
+		srv.RegisterService("p2p", mockAPI.P2P)
 	})
 	nd := nodebuilder.TestNode(t, node.Full, invokeRPC)
 	// start node
@@ -168,4 +174,5 @@ type mockAPI struct {
 	Fraud  *fraudMock.MockModule
 	Header *headerMock.MockModule
 	Das    *dasMock.MockModule
+	P2P    *p2pMock.MockModule
 }
diff --git a/core/testing.go b/core/testing.go
index 6253cf14..c8a56409 100644
--- a/core/testing.go
+++ b/core/testing.go
@@ -92,7 +92,10 @@ func StartTestCoreWithApp(t *testing.T) (tmservice.Service, Client) {
 
 	_, cleanupCoreNode, err := testnode.StartNode(tmNode, cctx)
 	require.NoError(t, err)
-	t.Cleanup(cleanupCoreNode)
+	t.Cleanup(func() {
+		err := cleanupCoreNode()
+		require.NoError(t, err)
+	})
 
 	endpoint, err := GetEndpoint(tmNode.Config())
 	require.NoError(t, err)
diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go
index 7d3bb81b..1aa88879 100644
--- a/header/p2p/exchange_test.go
+++ b/header/p2p/exchange_test.go
@@ -93,7 +93,7 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) {
 		servers[index], err = NewExchangeServer(hosts[index], store, protocolSuffix)
 		require.NoError(t, err)
 		servers[index].Start(context.Background()) //nolint:errcheck
-		exchange.peerTracker.connectedPeers[hosts[index].ID()] = &peerStat{peerID: hosts[index].ID()}
+		exchange.peerTracker.trackedPeers[hosts[index].ID()] = &peerStat{peerID: hosts[index].ID()}
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
@@ -291,7 +291,7 @@ func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (header.Exchan
 
 	ex, err := NewExchange(host, []peer.ID{tpeer.ID()}, "private")
 	require.NoError(t, err)
-	ex.peerTracker.connectedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID()}
+	ex.peerTracker.trackedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID()}
 	require.NoError(t, ex.Start(context.Background()))
 
 	t.Cleanup(func() {
diff --git a/header/p2p/peer_tracker_test.go b/header/p2p/peer_tracker_test.go
new file mode 100644
index 00000000..788ddbcf
--- /dev/null
+++ b/header/p2p/peer_tracker_test.go
@@ -0,0 +1,33 @@
+package p2p
+
+import (
+	"testing"
+	"time"
+
+	"github.com/libp2p/go-libp2p-core/peer"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestPeerTracker_GC(t *testing.T) {
+	h := createMocknet(t, 1)
+	gcCycle = time.Millisecond * 200
+	p := newPeerTracker(h[0], time.Millisecond*1, 1, 5)
+	pid1 := peer.ID("peer1")
+	pid2 := peer.ID("peer2")
+	pid3 := peer.ID("peer3")
+	pid4 := peer.ID("peer4")
+	p.trackedPeers[pid1] = &peerStat{peerID: pid1, peerScore: 0.5}
+	p.trackedPeers[pid2] = &peerStat{peerID: pid2, peerScore: 10}
+	p.disconnectedPeers[pid3] = &peerStat{peerID: pid3, pruneDeadline: time.Now()}
+	p.disconnectedPeers[pid4] = &peerStat{peerID: pid4, pruneDeadline: time.Now().Add(time.Minute * 10)}
+	assert.True(t, len(p.trackedPeers) > 0)
+	assert.True(t, len(p.disconnectedPeers) > 0)
+
+	go p.track()
+	go p.gc()
+	time.Sleep(time.Second * 1)
+	p.stop()
+	require.Nil(t, p.trackedPeers[pid1])
+	require.Nil(t, p.disconnectedPeers[pid3])
+}
diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go
index 3e834b47..68b471f1 100644
--- a/nodebuilder/header/module_test.go
+++ b/nodebuilder/header/module_test.go
@@ -71,6 +71,9 @@ func TestConstructModule_ExchangeParams(t *testing.T) {
 	require.Equal(t, exchange.Params.MinResponses, cfg.Client.MinResponses)
 	require.Equal(t, exchange.Params.MaxRequestSize, cfg.Client.MaxRequestSize)
 	require.Equal(t, exchange.Params.MaxHeadersPerRequest, cfg.Client.MaxHeadersPerRequest)
+	require.Equal(t, exchange.Params.MaxAwaitingTime, cfg.Client.MaxAwaitingTime)
+	require.Equal(t, exchange.Params.DefaultScore, cfg.Client.DefaultScore)
+	require.Equal(t, exchange.Params.MaxPeerTrackerSize, cfg.Client.MaxPeerTrackerSize)
 
 	require.Equal(t, exchangeServer.Params.WriteDeadline, cfg.Server.WriteDeadline)
 	require.Equal(t, exchangeServer.Params.ReadDeadline, cfg.Server.ReadDeadline)
diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go
index f3754034..51e0becc 100644
--- a/nodebuilder/node_test.go
+++ b/nodebuilder/node_test.go
@@ -37,13 +37,13 @@ func TestLifecycle(t *testing.T) {
 			require.NoError(t, err)
 
 			// ensure the state service is running
-			require.False(t, node.StateServ.IsStopped())
+			require.False(t, node.StateServ.IsStopped(ctx))
 
 			err = node.Stop(ctx)
 			require.NoError(t, err)
 
 			// ensure the state service is stopped
-			require.True(t, node.StateServ.IsStopped())
+			require.True(t, node.StateServ.IsStopped(ctx))
 		})
 	}
 }
diff --git a/nodebuilder/p2p/p2p_test.go b/nodebuilder/p2p/p2p_test.go
index da77ff45..a9b59901 100644
--- a/nodebuilder/p2p/p2p_test.go
+++ b/nodebuilder/p2p/p2p_test.go
@@ -29,14 +29,16 @@ func TestP2PModule_Host(t *testing.T) {
 
 	mgr := newModule(host, nil, nil, nil, nil)
 
+	ctx := context.Background()
+
 	// test all methods on `manager.host`
-	assert.Equal(t, []libpeer.ID(host.Peerstore().Peers()), mgr.Peers())
-	assert.Equal(t, libhost.InfoFromHost(peer).ID, mgr.PeerInfo(peer.ID()).ID)
+	assert.Equal(t, []libpeer.ID(host.Peerstore().Peers()), mgr.Peers(ctx))
+	assert.Equal(t, libhost.InfoFromHost(peer).ID, mgr.PeerInfo(ctx, peer.ID()).ID)
 
-	assert.Equal(t, host.Network().Connectedness(peer.ID()), mgr.Connectedness(peer.ID()))
+	assert.Equal(t, host.Network().Connectedness(peer.ID()), mgr.Connectedness(ctx, peer.ID()))
 	// now disconnect using manager and check for connectedness match again
-	assert.NoError(t, mgr.ClosePeer(peer.ID()))
-	assert.Equal(t, host.Network().Connectedness(peer.ID()), mgr.Connectedness(peer.ID()))
+	assert.NoError(t, mgr.ClosePeer(ctx, peer.ID()))
+	assert.Equal(t, host.Network().Connectedness(peer.ID()), mgr.Connectedness(ctx, peer.ID()))
 }
 
 // TestP2PModule_ConnManager tests P2P Module methods on
@@ -59,10 +61,10 @@ func TestP2PModule_ConnManager(t *testing.T) {
 	err = mgr.Connect(ctx, *libhost.InfoFromHost(peer))
 	require.NoError(t, err)
 
-	mgr.Protect(peer.ID(), "test")
-	assert.True(t, mgr.IsProtected(peer.ID(), "test"))
-	mgr.Unprotect(peer.ID(), "test")
-	assert.False(t, mgr.IsProtected(peer.ID(), "test"))
+	mgr.Protect(ctx, peer.ID(), "test")
+	assert.True(t, mgr.IsProtected(ctx, peer.ID(), "test"))
+	mgr.Unprotect(ctx, peer.ID(), "test")
+	assert.False(t, mgr.IsProtected(ctx, peer.ID(), "test"))
 }
 
 // TestP2PModule_Autonat tests P2P Module methods on
@@ -73,7 +75,7 @@ func TestP2PModule_Autonat(t *testing.T) {
 
 	mgr := newModule(host, nil, nil, nil, nil)
 
-	status, err := mgr.NATStatus()
+	status, err := mgr.NATStatus(context.Background())
 	assert.NoError(t, err)
 	assert.Equal(t, network.ReachabilityUnknown, status)
 }
@@ -111,11 +113,12 @@ func TestP2PModule_Bandwidth(t *testing.T) {
 	// connect to the peer
 	err = mgr.Connect(ctx, *libhost.InfoFromHost(peer))
 	require.NoError(t, err)
+
 	// check to ensure they're actually connected
-	require.Equal(t, network.Connected, mgr.Connectedness(peer.ID()))
+	require.Equal(t, network.Connected, mgr.Connectedness(ctx, peer.ID()))
 
 	// open stream with host
-	stream, err := peer.NewStream(ctx, mgr.Info().ID, protoID)
+	stream, err := peer.NewStream(ctx, mgr.Info(ctx).ID, protoID)
 	require.NoError(t, err)
 
 	// write to stream to increase bandwidth usage get some substantive
@@ -133,12 +136,12 @@ func TestP2PModule_Bandwidth(t *testing.T) {
 	// in the background process
 	time.Sleep(time.Second * 2)
 
-	stats := mgr.BandwidthStats()
+	stats := mgr.BandwidthStats(ctx)
 	assert.NotNil(t, stats)
-	peerStat := mgr.BandwidthForPeer(peer.ID())
+	peerStat := mgr.BandwidthForPeer(ctx, peer.ID())
 	assert.NotZero(t, peerStat.TotalIn)
 	assert.Greater(t, int(peerStat.TotalIn), bufSize) // should be slightly more than buf size due negotiations, etc
-	protoStat := mgr.BandwidthForProtocol(protoID)
+	protoStat := mgr.BandwidthForProtocol(ctx, protoID)
 	assert.NotZero(t, protoStat.TotalIn)
 	assert.Greater(t, int(protoStat.TotalIn), bufSize) // should be slightly more than buf size due negotiations, etc
 }
@@ -182,7 +185,7 @@ func TestP2PModule_Pubsub(t *testing.T) {
 	// anywhere where gossipsub is used in tests)
 	time.Sleep(1 * time.Second)
 
-	assert.Equal(t, len(topic.ListPeers()), len(mgr.PubSubPeers(topicStr)))
+	assert.Equal(t, len(topic.ListPeers()), len(mgr.PubSubPeers(context.Background(), topicStr)))
 }
 
 // TestP2PModule_ConnGater tests P2P Module methods on
@@ -193,10 +196,12 @@ func TestP2PModule_ConnGater(t *testing.T) {
 
 	mgr := newModule(nil, nil, gater, nil, nil)
 
-	assert.NoError(t, mgr.BlockPeer("badpeer"))
-	assert.Len(t, mgr.ListBlockedPeers(), 1)
-	assert.NoError(t, mgr.UnblockPeer("badpeer"))
-	assert.Len(t, mgr.ListBlockedPeers(), 0)
+	ctx := context.Background()
+
+	assert.NoError(t, mgr.BlockPeer(ctx, "badpeer"))
+	assert.Len(t, mgr.ListBlockedPeers(ctx), 1)
+	assert.NoError(t, mgr.UnblockPeer(ctx, "badpeer"))
+	assert.Len(t, mgr.ListBlockedPeers(ctx), 0)
 }
 
 // TestP2PModule_ResourceManager tests P2P Module methods on
@@ -207,7 +212,7 @@ func TestP2PModule_ResourceManager(t *testing.T) {
 
 	mgr := newModule(nil, nil, nil, nil, rm)
 
-	state, err := mgr.ResourceState()
+	state, err := mgr.ResourceState(context.Background())
 	require.NoError(t, err)
 
 	assert.NotNil(t, state)
diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go
index a7fb1e7b..0ea36842 100644
--- a/nodebuilder/testing.go
+++ b/nodebuilder/testing.go
@@ -48,8 +48,11 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti
 	cfg.Core.RPCPort = port
 	cfg.RPC.Port = "0"
 
+	// storePath is used for the eds blockstore
+	storePath := t.TempDir()
 	opts = append(opts,
 		state.WithKeyringSigner(TestKeyringSigner(t)),
+		fx.Replace(node.StorePath(storePath)),
 	)
 	nd, err := New(tp, p2p.Private, store, opts...)
 	require.NoError(t, err)
diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go
index 59325c1d..7d14ed94 100644
--- a/nodebuilder/tests/swamp/swamp.go
+++ b/nodebuilder/tests/swamp/swamp.go
@@ -103,8 +103,10 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp {
 
 	swp.t.Cleanup(func() {
 		swp.stopAllNodes(ctx, swp.BridgeNodes, swp.FullNodes, swp.LightNodes)
-		cleanupCoreNode()
-		cleanupGRPCServer()
+		err = cleanupCoreNode()
+		require.NoError(t, err)
+		err = cleanupGRPCServer()
+		require.NoError(t, err)
 	})
 
 	return swp
@@ -259,8 +261,12 @@ func (s *Swamp) newNode(t node.Type, store nodebuilder.Store, options ...fx.Opti
 	cfg, _ := store.Config()
 	cfg.Header.TrustedHash = s.trustedHash
 	cfg.RPC.Port = "0"
+
+	// tempDir is used for the eds.Store
+	tempDir := s.t.TempDir()
 	options = append(options,
 		p2p.WithHost(s.createPeer(ks)),
+		fx.Replace(node.StorePath(tempDir)),
 	)
 
 	node, err := nodebuilder.New(t, p2p.Private, store, options...)
diff --git a/share/availability/cache/availability_test.go b/share/availability/cache/availability_test.go
index 438d6392..6b3e9622 100644
--- a/share/availability/cache/availability_test.go
+++ b/share/availability/cache/availability_test.go
@@ -134,6 +134,6 @@ func (da *dummyAvailability) SharesAvailable(_ context.Context, root *share.Root
 	return nil
 }
 
-func (da *dummyAvailability) ProbabilityOfAvailability() float64 {
+func (da *dummyAvailability) ProbabilityOfAvailability(context.Context) float64 {
 	return 0
 }
diff --git a/share/eds/blockstore_test.go b/share/eds/blockstore_test.go
new file mode 100644
index 00000000..42f7ceab
--- /dev/null
+++ b/share/eds/blockstore_test.go
@@ -0,0 +1,66 @@
+package eds
+
+import (
+	"context"
+	"io"
+	"testing"
+
+	"github.com/filecoin-project/dagstore"
+	"github.com/ipld/go-car"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// TestBlockstore_Operations tests Has, Get, and GetSize on the top level eds.Store blockstore.
+// It verifies that these operations are valid and successful on all blocks stored in a CAR file.
+func TestBlockstore_Operations(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	t.Cleanup(cancel)
+
+	edsStore, err := newStore(t)
+	require.NoError(t, err)
+	err = edsStore.Start(ctx)
+	require.NoError(t, err)
+
+	eds, dah := randomEDS(t)
+	err = edsStore.Put(ctx, dah.Hash(), eds)
+	require.NoError(t, err)
+
+	r, err := edsStore.GetCAR(ctx, dah.Hash())
+	require.NoError(t, err)
+	carReader, err := car.NewCarReader(r)
+	require.NoError(t, err)
+
+	topLevelBS := edsStore.Blockstore()
+	carBS, err := edsStore.CARBlockstore(ctx, dah.Hash())
+	require.NoError(t, err)
+
+	blockstores := []dagstore.ReadBlockstore{topLevelBS, carBS}
+
+	for {
+		next, err := carReader.Next()
+		if err != nil {
+			require.ErrorIs(t, err, io.EOF)
+			break
+		}
+		blockCid := next.Cid()
+
+		for _, bs := range blockstores {
+			// test GetSize
+			has, err := bs.Has(ctx, blockCid)
+			require.NoError(t, err, "blockstore.Has could not find root CID")
+			require.True(t, has)
+
+			// test GetSize
+			block, err := bs.Get(ctx, blockCid)
+			assert.NoError(t, err, "blockstore.Get could not get a leaf CID")
+			assert.Equal(t, block.Cid(), blockCid)
+			assert.Equal(t, block.RawData(), next.RawData())
+
+			// test GetSize
+			size, err := bs.GetSize(ctx, blockCid)
+			assert.NotZerof(t, size, "blocksize.GetSize reported a root block from blockstore was empty")
+			assert.NoError(t, err)
+		}
+	}
+}
diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go
index 0e4db027..5eac847a 100644
--- a/share/eds/eds_test.go
+++ b/share/eds/eds_test.go
@@ -10,7 +10,7 @@ import (
 	"testing"
 
 	ds "github.com/ipfs/go-datastore"
-	blockstore "github.com/ipfs/go-ipfs-blockstore"
+	bstore "github.com/ipfs/go-ipfs-blockstore"
 	carv1 "github.com/ipld/go-car"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -86,7 +86,7 @@ func TestWriteEDSIncludesRoots(t *testing.T) {
 	f := openWrittenEDS(t)
 	defer f.Close()
 
-	bs := blockstore.NewBlockstore(ds.NewMapDatastore())
+	bs := bstore.NewBlockstore(ds.NewMapDatastore())
 	ctx, cancel := context.WithCancel(context.Background())
 	t.Cleanup(cancel)
 	loaded, err := carv1.LoadCar(ctx, bs, f)
@@ -148,7 +148,7 @@ func TestReadWriteRoundtrip(t *testing.T) {
 	f := openWrittenEDS(t)
 	defer f.Close()
 
-	loaded, err := ReadEDS(context.Background(), f, dah)
+	loaded, err := ReadEDS(context.Background(), f, dah.Hash())
 	require.NoError(t, err, "error reading EDS from file")
 	require.Equal(t, eds.RowRoots(), loaded.RowRoots())
 	require.Equal(t, eds.ColRoots(), loaded.ColRoots())
@@ -162,7 +162,7 @@ func TestReadEDS(t *testing.T) {
 	err = json.Unmarshal([]byte(exampleRoot), &dah)
 	require.NoError(t, err, "error unmarshaling example root")
 
-	loaded, err := ReadEDS(context.Background(), f, dah)
+	loaded, err := ReadEDS(context.Background(), f, dah.Hash())
 	require.NoError(t, err, "error reading EDS from file")
 	require.Equal(t, dah.RowsRoots, loaded.RowRoots())
 	require.Equal(t, dah.ColumnRoots, loaded.ColRoots())
@@ -174,7 +174,7 @@ func TestReadEDSContentIntegrityMismatch(t *testing.T) {
 	f := openWrittenEDS(t)
 	defer f.Close()
 
-	_, err := ReadEDS(context.Background(), f, dah)
+	_, err := ReadEDS(context.Background(), f, dah.Hash())
 	require.ErrorContains(t, err, "share: content integrity mismatch: imported root")
 }
 
@@ -202,7 +202,7 @@ func BenchmarkReadWriteEDS(b *testing.B) {
 				f := new(bytes.Buffer)
 				_ = WriteEDS(ctx, eds, f)
 				b.StartTimer()
-				_, err := ReadEDS(ctx, f, dah)
+				_, err := ReadEDS(ctx, f, dah.Hash())
 				require.NoError(b, err)
 			}
 		})
diff --git a/share/eds/ods_test.go b/share/eds/ods_test.go
index 8509fc04..75fad302 100644
--- a/share/eds/ods_test.go
+++ b/share/eds/ods_test.go
@@ -26,11 +26,11 @@ func TestODSReader(t *testing.T) {
 
 	// generate random eds data and put it into the store
 	eds, dah := randomEDS(t)
-	err = edsStore.Put(ctx, dah, eds)
+	err = edsStore.Put(ctx, dah.Hash(), eds)
 	require.NoError(t, err)
 
 	// get CAR reader from store
-	r, err := edsStore.GetCAR(ctx, dah)
+	r, err := edsStore.GetCAR(ctx, dah.Hash())
 	assert.NoError(t, err)
 
 	// create ODSReader wrapper based on car reader to limit reads to ODS only
@@ -75,11 +75,11 @@ func TestODSReaderReconstruction(t *testing.T) {
 
 	// generate random eds data and put it into the store
 	eds, dah := randomEDS(t)
-	err = edsStore.Put(ctx, dah, eds)
+	err = edsStore.Put(ctx, dah.Hash(), eds)
 	require.NoError(t, err)
 
 	// get CAR reader from store
-	r, err := edsStore.GetCAR(ctx, dah)
+	r, err := edsStore.GetCAR(ctx, dah.Hash())
 	assert.NoError(t, err)
 
 	// create ODSReader wrapper based on car reader to limit reads to ODS only
@@ -87,7 +87,7 @@ func TestODSReaderReconstruction(t *testing.T) {
 	assert.NoError(t, err)
 
 	// reconstruct EDS from ODSReader
-	loaded, err := ReadEDS(ctx, odsR, dah)
+	loaded, err := ReadEDS(ctx, odsR, dah.Hash())
 	assert.NoError(t, err)
 	require.Equal(t, eds.RowRoots(), loaded.RowRoots())
 	require.Equal(t, eds.ColRoots(), loaded.ColRoots())
diff --git a/share/eds/store_test.go b/share/eds/store_test.go
index e68fc880..709c4bf3 100644
--- a/share/eds/store_test.go
+++ b/share/eds/store_test.go
@@ -5,13 +5,13 @@ import (
 	"fmt"
 	"os"
 	"testing"
-
-	"github.com/stretchr/testify/assert"
+	"time"
 
 	"github.com/filecoin-project/dagstore/shard"
 	"github.com/ipfs/go-datastore"
 	ds_sync "github.com/ipfs/go-datastore/sync"
 	"github.com/ipld/go-car"
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
 	"github.com/celestiaorg/celestia-node/share"
@@ -33,11 +33,11 @@ func TestEDSStore_PutRegistersShard(t *testing.T) {
 	eds, dah := randomEDS(t)
 
 	// shard hasn't been registered yet
-	has, err := edsStore.Has(ctx, dah)
+	has, err := edsStore.Has(ctx, dah.Hash())
 	assert.False(t, has)
 	assert.Error(t, err, "shard not found")
 
-	err = edsStore.Put(ctx, dah, eds)
+	err = edsStore.Put(ctx, dah.Hash(), eds)
 	assert.NoError(t, err)
 
 	_, err = edsStore.dgstr.GetShardInfo(shard.KeyFromString(dah.String()))
@@ -58,7 +58,7 @@ func TestEDSStore_PutIndexesEDS(t *testing.T) {
 	stat, _ := edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String()))
 	assert.False(t, stat.Exists)
 
-	err = edsStore.Put(ctx, dah, eds)
+	err = edsStore.Put(ctx, dah.Hash(), eds)
 	assert.NoError(t, err)
 
 	stat, err = edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String()))
@@ -78,10 +78,10 @@ func TestEDSStore_GetCAR(t *testing.T) {
 	require.NoError(t, err)
 
 	eds, dah := randomEDS(t)
-	err = edsStore.Put(ctx, dah, eds)
+	err = edsStore.Put(ctx, dah.Hash(), eds)
 	require.NoError(t, err)
 
-	r, err := edsStore.GetCAR(ctx, dah)
+	r, err := edsStore.GetCAR(ctx, dah.Hash())
 	assert.NoError(t, err)
 	carReader, err := car.NewCarReader(r)
 
@@ -109,14 +109,14 @@ func TestEDSStore_Remove(t *testing.T) {
 
 	eds, dah := randomEDS(t)
 
-	err = edsStore.Put(ctx, dah, eds)
+	err = edsStore.Put(ctx, dah.Hash(), eds)
 	require.NoError(t, err)
 
 	// assert that file now exists
 	_, err = os.Stat(edsStore.basepath + blocksPath + dah.String())
 	assert.NoError(t, err)
 
-	err = edsStore.Remove(ctx, dah)
+	err = edsStore.Remove(ctx, dah.Hash())
 	assert.NoError(t, err)
 
 	// shard should no longer be registered on the dagstore
@@ -144,18 +144,78 @@ func TestEDSStore_Has(t *testing.T) {
 
 	eds, dah := randomEDS(t)
 
-	ok, err := edsStore.Has(ctx, dah)
+	ok, err := edsStore.Has(ctx, dah.Hash())
 	assert.Error(t, err, "shard not found")
 	assert.False(t, ok)
 
-	err = edsStore.Put(ctx, dah, eds)
+	err = edsStore.Put(ctx, dah.Hash(), eds)
 	assert.NoError(t, err)
 
-	ok, err = edsStore.Has(ctx, dah)
+	ok, err = edsStore.Has(ctx, dah.Hash())
 	assert.NoError(t, err)
 	assert.True(t, ok)
 }
 
+// TestEDSStore_GC verifies that unused transient shards are collected by the GC periodically.
+func TestEDSStore_GC(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	t.Cleanup(cancel)
+
+	edsStore, err := newStore(t)
+	edsStore.gcInterval = time.Second
+	require.NoError(t, err)
+
+	// kicks off the gc goroutine
+	err = edsStore.Start(ctx)
+	require.NoError(t, err)
+
+	eds, dah := randomEDS(t)
+	shardKey := shard.KeyFromString(dah.String())
+
+	err = edsStore.Put(ctx, dah.Hash(), eds)
+	require.NoError(t, err)
+
+	// doesn't exist yet
+	assert.NotContains(t, edsStore.lastGCResult.Load().Shards, shardKey)
+
+	// wait for gc to run, retry three times
+	for i := 0; i < 3; i++ {
+		time.Sleep(edsStore.gcInterval)
+		if _, ok := edsStore.lastGCResult.Load().Shards[shardKey]; ok {
+			break
+		}
+	}
+	assert.Contains(t, edsStore.lastGCResult.Load().Shards, shardKey)
+
+	// assert nil in this context means there was no error re-acquiring the shard during GC
+	assert.Nil(t, edsStore.lastGCResult.Load().Shards[shardKey])
+}
+
+func Test_BlockstoreCache(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	t.Cleanup(cancel)
+
+	edsStore, err := newStore(t)
+	require.NoError(t, err)
+	err = edsStore.Start(ctx)
+	require.NoError(t, err)
+
+	eds, dah := randomEDS(t)
+	err = edsStore.Put(ctx, dah.Hash(), eds)
+	require.NoError(t, err)
+
+	// key isnt in cache yet, so get returns errCacheMiss
+	shardKey := shard.KeyFromString(dah.String())
+	_, err = edsStore.cache.Get(shardKey)
+	assert.ErrorIs(t, err, errCacheMiss)
+
+	// now get it, so that the key is in the cache
+	_, err = edsStore.CARBlockstore(ctx, dah.Hash())
+	assert.NoError(t, err)
+	_, err = edsStore.cache.Get(shardKey)
+	assert.NoError(t, err, errCacheMiss)
+}
+
 func newStore(t *testing.T) (*Store, error) {
 	t.Helper()
 
diff --git a/state/core_access_test.go b/state/core_access_test.go
index 2bd148ed..5018cee4 100644
--- a/state/core_access_test.go
+++ b/state/core_access_test.go
@@ -14,19 +14,19 @@ func TestLifecycle(t *testing.T) {
 	err := ca.Start(ctx)
 	require.NoError(t, err)
 	// ensure accessor isn't stopped
-	require.False(t, ca.IsStopped())
+	require.False(t, ca.IsStopped(ctx))
 	// cancel the top level context (this should not affect the lifecycle of the
 	// accessor as it should manage its own internal context)
 	cancel()
 	// ensure accessor was unaffected by top-level context cancellation
-	require.False(t, ca.IsStopped())
+	require.False(t, ca.IsStopped(ctx))
 	// stop the accessor
 	stopCtx, stopCancel := context.WithCancel(context.Background())
 	t.Cleanup(stopCancel)
 	err = ca.Stop(stopCtx)
 	require.NoError(t, err)
 	// ensure accessor is stopped
-	require.True(t, ca.IsStopped())
+	require.True(t, ca.IsStopped(ctx))
 	// ensure that stopping the accessor again does not return an error
 	err = ca.Stop(stopCtx)
 	require.NoError(t, err)