Documentation
¶
Overview ¶
Package walrusds implements a Kubo (IPFS) datastore backed by Walrus, a decentralized storage network built on Sui, using a shared Postgres database as the durable key -> blob index.
Walrus is content-addressed (blobs are addressed by a content-derived blob ID, not by an arbitrary key), exposes no list/query API, and treats blobs as immutable with a finite, epoch-based lifetime. To present it as an IPFS datastore we keep the bytes on Walrus and the mapping
ds.Key -> { blobId, size, deletable, endEpoch, expiresAt }
in Postgres. Postgres is the source of truth for what the node "knows": it is shared across upload and retrieval nodes, survives local disk loss, and supports point-in-time recovery. Has/GetSize/Query are answered purely from Postgres so they never incur a Walrus round-trip; only Get touches the Walrus aggregator.
Index ¶
- Variables
- type Client
- func (c *Client) Read(ctx context.Context, blobID string) ([]byte, error)
- func (c *Client) ReadQuiltPatch(ctx context.Context, patchID string) ([]byte, error)
- func (c *Client) ReadRange(ctx context.Context, blobID string, offset, length int64) ([]byte, error)
- func (c *Client) Store(ctx context.Context, value []byte, epochs int, deletable bool) (StoreResult, error)
- func (c *Client) StoreQuilt(ctx context.Context, parts []QuiltPart, epochs int, deletable bool) (QuiltStoreResult, error)
- type ClientConfig
- type Config
- type Index
- type KeyRecord
- type ListItem
- type QuiltPart
- type QuiltPatch
- type QuiltStoreResult
- type Record
- type RenewItem
- type StoreResult
- type WalrusDatastore
- func (w *WalrusDatastore) Batch(_ context.Context) (ds.Batch, error)
- func (w *WalrusDatastore) Close() error
- func (w *WalrusDatastore) Delete(ctx context.Context, k ds.Key) error
- func (w *WalrusDatastore) Get(ctx context.Context, k ds.Key) ([]byte, error)
- func (w *WalrusDatastore) GetSize(ctx context.Context, k ds.Key) (int, error)
- func (w *WalrusDatastore) Has(ctx context.Context, k ds.Key) (bool, error)
- func (w *WalrusDatastore) Put(ctx context.Context, k ds.Key, value []byte) error
- func (w *WalrusDatastore) Query(ctx context.Context, q dsq.Query) (dsq.Results, error)
- func (w *WalrusDatastore) Sync(ctx context.Context, prefix ds.Key) error
Constants ¶
This section is empty.
Variables ¶
var ErrBlobNotFound = errors.New("walrusds: blob not found")
ErrBlobNotFound is returned by the Walrus client when the aggregator has no blob for the requested blob ID.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a small, context-aware HTTP client for the Walrus publisher (write) and aggregator (read) HTTP APIs. It supports multiple endpoints for failover and retries transient failures with exponential backoff.
We deliberately implement this directly instead of depending on a third-party SDK so that every request honours the caller's context (cancellation/timeouts) and so retry/failover behaviour is under our control.
func NewClient ¶
func NewClient(conf ClientConfig) *Client
NewClient builds a Walrus client from the supplied configuration.
func (*Client) Read ¶
Read fetches the bytes of the blob identified by blobID from a Walrus aggregator. It returns ErrBlobNotFound if the aggregator returns 404.
func (*Client) ReadQuiltPatch ¶ added in v0.2.0
ReadQuiltPatch fetches a single quilt member by its QuiltPatchID via the aggregator's GET /v1/blobs/by-quilt-patch-id/{id} endpoint. Only that member's bytes travel over the wire (the aggregator decodes just the patch's slivers), so packed blocks stay cheap to read. Results are cached in the byte-bounded LRU keyed by patch ID.
func (*Client) ReadRange ¶
func (c *Client) ReadRange(ctx context.Context, blobID string, offset, length int64) ([]byte, error)
ReadRange fetches the bytes [offset, offset+length) of the blob identified by blobID. It first issues an HTTP Range request so only the requested block travels over the wire. If the aggregator ignores the Range header and returns the whole blob (HTTP 200), the full body is cached and sliced locally, so packed blocks remain cheap to read even without range support.
func (*Client) Store ¶
func (c *Client) Store(ctx context.Context, value []byte, epochs int, deletable bool) (StoreResult, error)
Store uploads value to a Walrus publisher, keeping it alive for the given number of epochs. When deletable is true the blob is registered as deletable so it can later be removed on-chain.
func (*Client) StoreQuilt ¶ added in v0.2.0
func (c *Client) StoreQuilt(ctx context.Context, parts []QuiltPart, epochs int, deletable bool) (QuiltStoreResult, error)
StoreQuilt batches many small blobs into a single Walrus quilt via the publisher's PUT /v1/quilts endpoint (multipart/form-data, one file part per member keyed by its identifier). A quilt shares one Walrus blob's overhead (Sui storage object, gas, erasure-coding metadata) across all its members, which is dramatically cheaper than one blob per small block. The returned QuiltStoreResult carries the quilt's blob ID plus each member's QuiltPatchID for later individual retrieval.
type ClientConfig ¶
type ClientConfig struct {
// PublisherURLs are Walrus publisher (write) base URLs. At least one is
// required for Put to work.
PublisherURLs []string
// AggregatorURLs are Walrus aggregator (read) base URLs. At least one is
// required for Get to work.
AggregatorURLs []string
// RequestTimeout bounds a single HTTP attempt. Zero means no per-attempt
// timeout beyond the caller's context.
RequestTimeout time.Duration
// MaxRetries is the number of additional attempts (per endpoint set) on
// transient failures. Zero means a single attempt.
MaxRetries int
// BlobCacheBytes is the byte budget for the in-memory LRU of whole blobs
// used to satisfy range reads of packed blocks. Zero disables the cache.
BlobCacheBytes int64
}
ClientConfig configures a Walrus Client.
type Config ¶
type Config struct {
// PublisherURLs are Walrus publisher (write) base URLs (comma-separated
// values are split by the plugin). At least one is required.
PublisherURLs []string
// AggregatorURLs are Walrus aggregator (read) base URLs. At least one is
// required.
AggregatorURLs []string
// PostgresURL is the database/sql connection string for the shared index,
// e.g. "postgres://user:pass@host:5432/db?sslmode=require".
PostgresURL string
// Table is the index table name. Defaults to "walrus_index".
Table string
// Epochs is how many storage epochs new blobs are paid for. Defaults to 1.
Epochs int
// Deletable registers blobs as deletable on Walrus. Defaults to false.
Deletable bool
// Workers is the Batch.Commit() concurrency. Defaults to defaultWorkers.
// Peak upload memory is roughly Workers × PackTargetSize, so raise both
// Workers and host RAM together.
Workers int
// MaxOpenConns bounds the Postgres connection pool. Defaults to
// defaultMaxOpenConns. Keep it >= Workers so committing packs does not
// starve on connections; a value <= 0 applies the default.
MaxOpenConns int
// PackTargetSize is the target size (in bytes) of a packed Walrus blob.
// During Batch.Commit, blocks are grouped into packs up to this size and
// uploaded as a single Walrus quilt (or, with DisableQuilt, a concatenated
// blob), amortizing the per-blob Walrus cost (Sui gas + WAL minimums) across
// many IPFS blocks. A block larger than this gets its own blob. Defaults to
// defaultPackTargetSize.
PackTargetSize int64
// DisableQuilt reverts batch packing to the legacy scheme: blocks are
// concatenated into one opaque blob and read back by byte range. By default
// (false) batches are stored as Walrus quilts, which share blob overhead
// natively and are read back per-member by QuiltPatchID. Existing rows
// written under either scheme keep working regardless of this setting.
DisableQuilt bool
// BlobCacheBytes is the byte budget for the in-memory LRU of whole blobs
// used to serve range reads of packed blocks. Defaults to
// defaultBlobCacheBytes; a negative value disables the cache.
BlobCacheBytes int64
// RequestTimeout bounds a single Walrus HTTP attempt. Defaults to 60s.
RequestTimeout time.Duration
// MaxRetries is the number of retries per Walrus request. Defaults to 3.
MaxRetries int
// EpochDuration is the wall-clock length of one Walrus storage epoch. When
// non-zero (together with RenewInterval) it enables the renewal worker,
// which re-uploads blobs before their paid storage expires. Operators set
// this to match the target network (e.g. ~14 days on mainnet).
EpochDuration time.Duration
// RenewInterval is how often the renewal worker scans for expiring blobs.
// Zero disables renewal.
RenewInterval time.Duration
// RenewLead is how far ahead of expiry a blob is renewed. Defaults to one
// EpochDuration when zero and renewal is enabled.
RenewLead time.Duration
}
Config holds everything needed to construct a WalrusDatastore.
type Index ¶
type Index interface {
Put(ctx context.Context, key string, rec Record) error
PutMany(ctx context.Context, recs []KeyRecord) error
Get(ctx context.Context, key string) (Record, error)
Delete(ctx context.Context, key string) error
DeleteMany(ctx context.Context, keys []string) error
List(ctx context.Context, prefix string, limit, offset int) ([]ListItem, error)
DueForRenewal(ctx context.Context, before time.Time, limit int) ([]RenewItem, error)
UpdateBlobAfterRenewal(ctx context.Context, oldBlobID, newBlobID string, endEpoch uint64, expiresAt sql.NullTime) error
Close() error
}
Index is the durable key -> blob mapping. It is intentionally an interface so the backend (Postgres today, DynamoDB/SQLite later) can be swapped without touching the datastore logic.
type QuiltPart ¶ added in v0.2.0
QuiltPart is a single small blob (an IPFS block) to be batched into a quilt. Identifier is the unique name of the part within the quilt; the store response echoes it back paired with the part's QuiltPatchID.
type QuiltPatch ¶ added in v0.2.0
QuiltPatch is one stored member of a quilt: the identifier we supplied and the QuiltPatchID assigned by Walrus, which is how the part is read back.
type QuiltStoreResult ¶ added in v0.2.0
type QuiltStoreResult struct {
QuiltID string
EndEpoch uint64
Patches []QuiltPatch
}
QuiltStoreResult is the parsed result of a store-quilt call: the quilt's own blob ID (used for renewal/extension, since the quilt is itself one Walrus blob), the epoch its paid storage ends, and the per-part patch IDs.
type Record ¶
type Record struct {
BlobID string
PatchID string
Offset int64
Size int64
Deletable bool
EndEpoch uint64
ExpiresAt sql.NullTime
}
Record is the metadata we persist in the shared index for each IPFS key. It is everything needed to (a) locate the block's bytes within a Walrus blob, (b) answer Has/GetSize without touching Walrus, and (c) drive epoch renewal.
Several keys can share a single Walrus blob. There are two packing schemes, distinguished by PatchID:
- Quilt rows (PatchID != ""): the block is a member ("patch") of a Walrus quilt. BlobID is the quilt's own blob ID (used for renewal grouping) and PatchID is the QuiltPatchID used to read the member back. Offset is unused.
- Concat/plain rows (PatchID == ""): the block lives at the byte range [Offset, Offset+Size) inside the blob BlobID. Unpacked blocks and all legacy rows are this shape with Offset == 0 and Size == the block length.
type RenewItem ¶
type RenewItem struct {
BlobID string
}
RenewItem identifies a Walrus blob whose paid storage is approaching expiry. Renewal operates per blob (not per key) so a packed blob holding many blocks is re-uploaded exactly once.
type StoreResult ¶
StoreResult is the subset of a Walrus publisher "store" response that we care about: the resulting blob ID and the epoch at which the blob's paid storage ends.
type WalrusDatastore ¶
type WalrusDatastore struct {
// contains filtered or unexported fields
}
WalrusDatastore stores values on Walrus and keeps the durable key -> blob mapping in Postgres.
func NewWalrusDatastore ¶
func NewWalrusDatastore(conf Config) (*WalrusDatastore, error)
NewWalrusDatastore validates the configuration, connects to Postgres (creating the index table if needed), prepares the Walrus client and, if configured, starts the background renewal worker.
func (*WalrusDatastore) Batch ¶
Batch buffers Put/Delete operations and applies them concurrently on Commit.
func (*WalrusDatastore) Close ¶
func (w *WalrusDatastore) Close() error
Close stops the renewal worker and closes the Postgres connection.
func (*WalrusDatastore) Delete ¶
Delete removes the index entry for k. It does not delete the underlying Walrus blob: on-chain deletion requires a Sui key and is out of scope for this datastore. The blob becomes unreferenced and eventually expires. Delete is idempotent.
func (*WalrusDatastore) Get ¶
Get resolves the blob ID for k in Postgres and fetches the bytes from the Walrus aggregator. Returns ds.ErrNotFound when k is unknown to the index.
func (*WalrusDatastore) GetSize ¶
GetSize returns the stored size for k, answered entirely from Postgres.
func (*WalrusDatastore) Put ¶
Put uploads value to Walrus and records the resulting blob ID and metadata in Postgres. The Walrus upload happens first: if the index write then fails, the blob exists but is unreferenced (a recoverable leak), which is strictly safer than an index row pointing at a blob that was never stored.