From 309885dc21e9c7935486bdd5d863bc12cda2be60 Mon Sep 17 00:00:00 2001 From: jbenet Date: Thu, 1 Nov 2018 13:05:27 -0700 Subject: [PATCH 1/2] draft --- selectors/selectors.jpg | Bin 0 -> 49737 bytes selectors/selectors.md | 404 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 404 insertions(+) create mode 100644 selectors/selectors.jpg create mode 100644 selectors/selectors.md diff --git a/selectors/selectors.jpg b/selectors/selectors.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a96f0076fa3264d90f6536628ccd5a2341471c27 GIT binary patch literal 49737 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 afc^7|{u__~oly$^Weecn@LdW2p8G%bQj>=O literal 0 HcmV?d00001 diff --git a/selectors/selectors.md b/selectors/selectors.md new file mode 100644 index 00000000..d210178e --- /dev/null +++ b/selectors/selectors.md @@ -0,0 +1,404 @@ +# IPLD Selectors + +This document is a designdoc for IPLD Selectors. + +## [Meta: Status of this doc] + +- This was written around 2018-10-16 ([video presentation](https://drive.google.com/file/d/1NbbVxZQFKXwW6mdodxgTaftsI8eID-c1/view)) +- It narrows down the decision space enough to make significant progress. +- good enough for trying out an implementation to learn more and make choices. +- But it is not complete. +- some choices that need to be made: + - [ ] select general binary and string format structure for selectors. (options given here) + - [ ] binary and string formats for each selector. doesn't have to be here. + - [ ] whether to dump all selector codes into multicodec table, or one code. + - [ ] which S expression selector variant to use (may be out of scope for this doc) +- more prose here may help implementors. + +## Motivation - what are Selectors? + +*Prerequisites: [IPLD](https://github.com/ipld/ipld), IPLD data model, [CID](https://github.com/ipld/cid).* + +IPLD Selectors are expressions that identify ("select") a subset of nodes in an IPLD dag. + +This is a useful primitive to use along with: (a) systems that require distributing or pinning dags (IPFS, Filecoin, bitswap, graphsync, ipfs-cluster), (b) applications that require fetching subsets of data in specific orders or at specific times (video players, dataset viewers, file systems), (c) programs that transform graphs into other graphs (data transformations, ETL, etc). In short, it is a fundamental primitive required by most systems and applications in the IPLD and IPFS ecosystems, as important as [multihash](https://github.com/multiformats/multihash), [CIDs](https://github.com/ipld/cid), [IPLD Formats](https://github.com/ipld/), and more. + +![](./selectors.jpg) + +## Prior Work + +There have been a lot of articulations about selectors in the history of the IPFS and IPLD projects. Many documents exist extolling all the kinds of use cases for selectors. Instead of giving a complete articulation here, this document will mention a few use cases and link to the other documents. + +- [Designing IPLD Selectors (2017)](https://github.com/ipld/specs/issues/35) +- [IPLD Selector Thoughts (2017)](https://github.com/ipfs/notes/issues/272) +- [exprimental js-ipld-selector (2017)](https://github.com/ipld/js-ipld-selector) +- [Designing IPLD Selectors - Workshop (2016)](https://github.com/ipfs/2016-IPFS-Workshop-Lisbon/issues/5) (+ [notes](https://github.com/ipld/specs/issues/4)) +- [IPLD Selector (original issue) (2015)](https://github.com/ipfs/notes/issues/12) + +### Important Design Notes + +**Learn from the best.** There have been dozens of graph selector systems implemented over the last few decades. There have been only a few that have been extremely successful, and productive. Study and learn from these systems. These include: unix globs, regular expressions, XPath, css selectors, and more. + +**Selectors include paths to the root.** In most cases using IPLD Selectors, programs need to be able to verify authenticated data structures (they need to hash all the nodes in a path, all the way to the root). Therefore, for all these distributed, authenticated use cases, it is drastically easier to count on Selectors that yield a result that is verifiable (checkable against the original query -- the root). + +**Self-describe and use multiple types.** Over the years, there has been much designing, without arriving at a complete, perfect solution. We have now recognized this as a feature where self-description and upgradability fit better than forcing a single language: there should just be many types of selectors and applications should use the ones that fit their needs. Due to the wide variety of use cases, we will not have a single perfect syntax. This will become easier over time, as systems that use IPLD acquire the ability to execute authenticated code. + +**Aim for succinct, intuitive, human-readable, expressive power.** The most successful selector systems (unix globs, regular expressions, css selectors, etc.) have created very poweful and succinct expressions, that balance the nuance between making something human-friendly and highly efficient. Understandability, intuitiveness, expressivity, familiarity, and similar qualities are desired in the human-readable syntax. Self-description and types make this drastically easier, enabling whole new types to be made over time as better and better syntaxes are discovered, and even existing familiar syntaxes to be ported over. + +**Aim for succinct, efficient, self-describing binary representations.** Various systems that use IPLD Selectors may need to distribute and store billions of selectors. Succinctness of expression is key. Self-description and types make this drastically easier, enabling whole new types to be made over time as more efficient syntaxes are discovered. + +**Blocks have CIDs. IPLD Nodes have Paths.** It is very important that we align on the following two statements: (1) Blocks have CIDs. (2) IPLD Nodes have paths, not CIDs. This is important because it means that IPLD nodes may begin within the middle of a block, and may not be addressable directly by a CID. This is a key realization, as Selectors must always support Paths as a dag root, not just a CID. + +## IPLD Selector System + +### Narrowing down the problem + +The problem of IPLD Selectors is narrowed down to a very concrete, simple problem: + +> - How can we succinctly identify a dag subset, connected to the root? + +These other questions are solved in other systems, above or below IPLD Selectors: + +> - How can we represent arbitrary data structures or files in dags? (IPLD data model, user code) +> - How can we succinctly identify a dag subset, not connected to the root (User code on top of IPLD selectors) +> - How can we traverse a dag subset? (IPLD library implementations, using IPLD Selectors) +> - How can we make subset selection efficient? (IPLD library implementations) +> - How can we make selector combination efficient? (IPLD library implementations) +> - How can we strip a dag subset from links to objects not in the subset (IPLD Transformations, user code) +> - How can we convert one dag representation into another? (IPLD Transformations, user code) +> - How can we agree with multiple other parties who will pin or send subets of a dag? (ipfs-cluster, graphsync) + +With our problem narrowed down, we can focus on solving just that, and let all other concerns be solved elsewhere in the stack. + +### Selector Requirements + +- P0 - Selectors can express any valid dag subset (connected to the root). +- P0 - Selectors can select based on IPLD paths. +- P1 - Selectors can select based on values. +- P2 - Selectors can select with seeded pseudorandomness. +- P3 - Selectors can select based on sibling nodes (as in css3) and their descendants (as in css4). +- P1 - Selectors can be composed. +- P0 - Once written, selectors should work permanently. (selector code should not change under the users). +- P1 - Selector languages can evolve and improve over time (faster than other selector systems). +- P3 - Allow experimentation without requiring backward-compatible syntax (different from globs and css). +- P0 - There is a 1-1 mapping between human readable and binary syntaxes. +- P0 - Binary syntax is self-described, succinct, and efficient. +- P0 - Human readable syntax is powerful and expressive. +- P1 - Human readable syntax is intuitive and familiar. + +Some of these may seem mutually exclusive. They are not. + +## Approach: Selector Types + +The requirements stated above are hard to meet. We have spent lots of time in the last few years trying to reconcile them into one language and syntax, with no success. Earlier this year (2018) we recognized that the solution to this problem should be flexibility and interoperability: let many selector languages and syntaxes flourish, and let them evolve over time. This would allow us to satisfy all constraints above, including both a permanent model that can also improve over time. And it reduces the core of our system into three components: + +- (1) A system of selector types, that allows creating new selector languages and syntaxes, and can compose them. +- (2) An easy path for plugging selector types into IPLD libraries and other consumers of IPLD Selectors. +- (3) A light process for testing and admitting new selector types into standard IPLD libraries. + +These components imply or expand into the following things: + +- Well-defined binary and human-readable type self-description (codes in multicodec). +- A narrow `Selector` interface for most uses of selectors, agnostic to selector type. +- A standard way to add selector type implementations to IPLD libraries +- IPLD libraries that pervasively uses the abstract `Selector` type, and can plug in concrete types. +- A few simple selector types that cover most common cases (cid, path, glob, ...) +- A selector type to allow composing selectors (MultiSelector) +- Aim for language independent implementations of selector implementations (parsers, execution, etc). (WASM?) +- Allow language-specific implementations of selectors (parsers, execution, etc). +- (IMPORTANT) Well-designed set of test vectors representing a variety of use cases for IPLD Selectors. +- A recommended structure for implementing a selector type, with an easy to use test suite. + + +## Selector Types + +Selectors have multiple types + +``` + +0, null, selects the empty set +1, cid, selects a specific cid +2, path, selects an ipld path +3, glob, selects a unix-style glob +4, multi, combines multiple selectors +5, css, selects in css style +6, git, selects with git tilde notation +7, bitfield, selects all the nodes expressed in a bitfield +``` + +**TODO**: put selectors into the grand magnificent global universal multicodec table... + +### Cid Selector + +This is a basic selector, that means exactly a single CID, and thus a single graph node. Nothing more. + +```go +// Binary: +// or: +// String: /sel/cid/ +type CidSelector struct { + Cid Cid // el cid +} +``` + +### Path Selector + +This is a selector w/ a path. Absolute ones must start with a CID + +```go +// Binary: +// or: +// String: /sel/path//a/b/c/d/... +type PathSelector struct { + Path Path +} +``` + +### Glob Selector + +This is a selector with a [unix shell glob](https://en.wikipedia.org/wiki/Glob_(programming)) style thing. Absolute ones must start with a CID. It supports the following syntax: + +- `*` - means any number of single characters, except `/` -- ie matches any link within a level (as in shell `*`) +- `**` - means any decendant (as in shell `**`) +- `?` - matches any single character +- `{abc,def}` - means either `abc` or `def` +- [abc] - matches one character in the bracket set. +- [a-z] - matches one character from the (locale-dependent) range given in the bracket. + +```go +// Binary: +// or: +// String: /sel/path//a/*/** +type GlobSelector struct { + Glob Path +} +``` + +### Multi Selector + +This is a selector that combines other selectors. It is a set expression. + +- `union` ($$ A \cup B $$) - takes all nodes expressed by either selectors (A _or_ B) +- `intersection` ($$ A \cap B $$) - takes all nodes expressed by both selectors (A _and_ B) +- `difference` ($$ A - B $$) - takes all nodes expressed in A, except those expressed in B (A *except* B) + +```go +// Binary: +// or: +// String: /sel/multi/(string sel sexp) +// no can do +type MultiSelector struct { + Op Operator + Sels []Selector +} + +type Operator int +const ( + OpUnion Operator = 1 + OpIntersection Operator = 2 + OpDifference Operator = 3 +) +``` + +#### Selector S-expressions (`sel-sexp`) + +[S-expressions](https://en.wikipedia.org/wiki/S-expression) are a simple way to combine objects into nested expressions. We define this in order to have Multi Selectors. A selector s-expression (`sel-sexp`) is defined as follows. + +Option 1 - simpler, need to nest another multiselector to nest another expression (this seems better). + +``` + ::= ()+ +``` + +Option 2: more complicated at the `sel-sexp` level, but avoids having to embed the multiselector preamble. + +``` + ::= | + | + ()+ + + ::= 1 (union) | 2 (intersection) | 3 (difference) + ::= 0 + ::= 1 + ::= 2 or more (in a varint) +``` + +## IPLD Interfaces in Go with Selectors + +This section presents a set of possible interfaces in Go, using IPLD dags and selectors. The main purpose of this example is to show how the Selector interfaces might work in practice. + +```go +const ( + // ErrNotFound is returned when an object is not found at a particular dag. + ErrNotFound = errors.New("not found") + + // ErrInvalidPath is returned when a path is not + ErrInvalidPath = errors.New("invalid path") +) + +// DAG is an object that provides access to an entire IPLD dag. +// It provides a handle to a set of data, all of which is rooted at one +// node (the Root). +// The interface is very simple, it only provides the Root, GetNode to retrieve +// any sub-object by path, and Traverse, to use in algorithms that need to +// sub-select or visit the entire dag. +// This could be entirely in memory, lazy-loaded, backed by a DagService, +// or be a proxy to a remote dag. +type DAG interface { + // Root returns the Path of the ipld node that is the root of this DAG + // We return the Path to avoid returning an error, as returning + // the node itself may fail. + Root() Path + + // GetNode returns an object identified by path. + // Path may be absolute (start with a CID), or relative (from the root), + // similar to how directories work in Unix filesystems. + // Either way, the node returned MUST be a descendant of Root(). + // rest is the remainder if the Path, if any is left. if rest is not empty, + // then err is not nil (ErrInvalidPath or ErrNotFound). + // The return error may be ErrNotFound, ErrInvalidPath, or errors + // propagated from underlying systems. + GetNode(path Path) (n Node, rest Path, err error) + + // Traverse returns a Traverser object to walk through this whole Dag. + // See the Traverser documentation for how to use it. + Traverse() Traverser +} + + +// Traverser is an object that traverses a particular dag. Think of it like +// an iterator over the whole dag. This object is in principle similar to +// traversals like os.Walk, to iterators in c++/rust, to generators in Python, +// etc. +// +// The implementation of a traverser is highly Dag depenedent, thus usually +// an implementation of Dag will have a specific Traverser associated with it. +// +// Traversers support filtering via selectors, which is an important feature. +// Instead of requiring wrapping/nesting traversers (expensive), we allow +// filtering a single traverser with multiple selectors instead (much cheaper). +// This allows the logic of checking whether to consider a node to be applied +// efficiently, ideally before the node is fetched. +// +// TODO: +// - address skipping links, as in os.Walk. Maybe turn into a walk function. +// - return only the Path, not the node (avoid retrieving it if not needed?). +// this is similar to how os.Walk works. +type Traverser interface { + // Next returns the next node visited by this Traverser + Next() (Path, Node, error) + + // Filter adds a new selector to filter the nodes the Traverser will return + Filter(sel Selector) +} + +// Selector is an object that expresses a sub-selection on a graph. +// +// For a more complete articulation of selectiors, read the IPLD selector +// design document. +// +// Note that selectors have different types, and each type will need its +// own Go object. User code should use the selector interface and avoid +// assuming a specific type of selector. Types are identified using a code, +// agreed upon in the multicodec table. +// +// The selector interface in Go is a simple construct that enables reducing +// a dag to a subset represented in a concise selector expression. All it +// does is check whether a path is included in the selector (IncludesPath). +// Using this, other objects (like the Traverser) can reduce a Dag to only +// the subdag represented by the selector. +// +// Important note: selectors select a subdag that is also connected to the +// same dag root. Whatever nodes are selected by the selector must +// include paths going all the way back to the root. There are no nodes +// selected that are disconnected from the root -- at least one of their parents +// (linked-from relationship) must also be selected. +// +// Serialization +// - Selectors have both string and binary representations. These are canonical +// and can be used in a variety of places. +// - The binary representation should be used in memory, on the wire, on disk, etc. +// - The string representation should be used whenever a selector is shown to the +// user, or when the user is inputting a selector. It can also be used in other +// formats that aim to have high human readability (eg package.json, etc). +type Selector interface { + // IncludesPath returns whether path p is included in this selector, + // as applied to dag d. + // + // d is necessary in order to have a handle to the + // whole dag. Selector objects themselves should be cheap and not + // link to huge amounts of data. + IncludesPath(d Dag, p Path) bool + + // IsAbsolute returns whether the selector is absolute (rooted at a specific + // cid in the serialized expression) or not. If it's not, it is relative, + // which means there is no cid in the expression, and thus the selector + // root is the root of the dag it is applied to. Intuitively, this works + // exactly the same as shell globs in unix filesystems: + // - Absolute: /foo/bar/baz/**/* -> /bar/baz/**/* + // - Reltaive: baz/**/* -> baz/**/* + IsAbsolute() bool + + // Root returns the root Cid of this selector. + // It may be nil if the selector is not absolute. + Root() Cid + + // Type returns the selector type code. + Type() SelectorType + + // Bytes returns the binary packed serialized representation of this selector. + // Use this for all machine-oriented input/output (on the wire, disk, etc.) + Bytes() []byte + + // String returns the string serialized representation of this selector. + // Use this for all human readable input/output (terminal, etc). + String() string +} + +// SelectorType is the enum used for selector type codes. These type codes +// are part of the protocol and housed in the multicodec table. +type SelectorType int +const ( + NullSelector SelectorType = 0 + CidSelector SelectorType = 1 + PathSelector SelectorType = 2 +) + +// Select is a function that applies a selector to a dag, returning the resulting +// subdag. It is implemented using a Traverser. Given a lazy traverser +// implementation, it should be a lazy application and avoid visiting the whole +// dag until it needs to. +func Select(d Dag, s Selector) Dag { + t := d.Traverse() + t.Filter(s) + return &traversedDag{d, s, t} +} + +// traversedDag is a utility dag implementation that uses a traverser to +// to apply a selector. this is what Select uses. +type traversedDag struct { + d Dag + s Selector + t Traverser +} + +func (d *traversedDag) Root() Node { + if d.s.IncludesPath(RootPath) { + return d.d.Root() + } + return nil +} + +func (d *traversedDag) GetNode(path Path) Node { + if d.s.IncludesPath(path) { + return d.d.GetNode(path) + } + return nil +} + +func (d *traversedDag) Traverse() Traverser { + return WrapTraverser(d.t) +} +``` + + From b91af5a09ee56052841a749d2e548d562dbac93d Mon Sep 17 00:00:00 2001 From: jbenet Date: Thu, 1 Nov 2018 14:06:54 -0700 Subject: [PATCH 2/2] graphsync --- graphsync/graphsync.md | 151 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 graphsync/graphsync.md diff --git a/graphsync/graphsync.md b/graphsync/graphsync.md new file mode 100644 index 00000000..df7b00bf --- /dev/null +++ b/graphsync/graphsync.md @@ -0,0 +1,151 @@ +# graphsync + +A protocol to synchronize graphs across peers. + +See also [ipld](../IPLD.md), [IPLD Selectors](../selectors/selectors.md) + +## [Meta: Status of this doc] + +- This was written around 2018-10-16 ([video presentation](https://drive.google.com/file/d/1NbbVxZQFKXwW6mdodxgTaftsI8eID-c1/view)) +- This document is unfortunately far from complete. :( I want to finish this by EOQ. +- But this document provides enough information for an implementation to be made by someone who has already implemented bitswap (or understands it well). +- It relies heavily on an understanding of bitswap as it is now. It likely won't be useful to people without a good understanding of how Bitswap works at the moment. +- This requires IPLD Selectors to exist and be implemented. + +## Concepts and Glossary + +- `peer` - a program or process participating in the graphsync protocol. It can connect to other peers. +- `graph` - an authenticated directed acyclic graph (DAG) of content. an IPLD dag. consists of nodes with hash (content addressed, authenticated) links to other nodes. ($$ G $$) +- `dag` - a directed acyclic graph. For our purposes, our DAGs are all IPLD (connected by hash links, authenticated, content addressed, etc.) +- `selector` - an expression that identifies a specific subset of a graph. ($$ S(G) \subset G $$) +- `selector language` - the language defining a family of selectors +- `request` - a request for content from one `peer` to another. This is similar to HTTP, RPC, or API requests. +- `response` - the content sent from `responder` to `requester` fulfilling a `request`. +- `requester` - the peer which initiates a `request` (wants content). +- `responder` - the peer receiving a `request`, and providing content in a `response` (provides content). +- `request process` - a request and its fulfillment is a sub-process, a procedure call across peers with the following phases (at a high level): + - (1) The `requester` initiates by sending a request message (`req`) to the `responder`, specifying desired content and other request parameters. + - (2) Upon receiving a request message, the `responder` adds the request to a set of active requests, and starts processing it. + - (3) The `responder` fulfills the request by sending content to the `requester` (the `response`) . + - (4) The `responder` and `requester` can terminate the request process at any time. + - Notes: + - We are explicitly avoiding the `client-server` terminology to make it clear that `requester` and `responder` are "roles" that any peer might play, and to avoid failling in the two-sided client-server model of the web.. + - `requests` may be short or long-lived -- requests may be as short as microseconds or last indefinitely. +- `priority` - a numeric label associated with a `request` implying the relative ordering of importance for requests. This is a `requester's` way of expressing to a `responder` the order in which the `requester` wishes the `requests` to be fulfilled. The `responder` SHOULD respect `priority`, though may return `responses` in any order. + +## Interfaces + +This is a listing of the data structures and process interfaces involved in the graphsync protocol. For simplicity, we use Go type notation, though of course graphsync is language agnostic. + +```go +type Graphsync interface { + Request(req Request) (Response, error) +} + +type Request struct { + Selector Selector + Priority Priority // optional + Expires time.Duration // optional +} + +type GraphSyncNet interface { + SendMessage(m Message) + RecvMessage(m Message) +} +``` + + +## Network Messages + +```protobuf +message GraphsyncMessage { + + message Request { + int32 id = 1; // unique id set on the requester side + bytes selector = 2; // ipld selector to retrieve + bytes extra = 3; // aux information. useful for other protocols + int32 priority = 4; // the priority (normalized). default to 1 + bool cancel = 5; // whether this cancels a request + } + + message RequestList { + repeated Request reqs = 1; // a list of requests + bool full = 2; // whether this is the full RequestList. default to false, for incremental requests. + } + + message Response { + int32 id = 1; // the request id + int32 status = 2; // a status code. + bytes extra = 3; + } + + message Block { + bytes prefix = 1; // CID prefix (cid version, multicodec and multihash prefix (type + length) + bytes data = 2; + } + + // the actual data included in this message + RequestList reqlist = 1; + repeated Response reslist = 2; + repeated Block data = 2; +} +``` + +### Response Status Codes + +``` +# info - partial +10 Request Acknowledged. Working on it. +11 Additional Peers. PeerIDs in extra. +12 Not enough vespene gas ($) +13 Other Protocol - info in extra. + +# success - terminal +20 Request Completed, full content. +21 Request Completed, partial content. + +# error - terminal +30 Request Rejected. NOT working on it. +31 Request failed, busy, try again later (getting dosed. backoff in extra). +32 Request failed, for unknown reason. Extra may have more info. +33 Request failed, for legal reasons. +34 Request failed, content not found. +``` + +## Example Use Cases + +### Syncing a Blockchain + +Requests we would like to make for this: + +- Give me `/Parent`, `/Parent/Parent` and so on, up to a depth of `N`. +- Give me nodes that exist in `` but not `` + - In addition to this, the ability to say "Give me some range of (the above query) is very important". For example: "Give me the second 1/3 of the nodes that are children of `` but not ``" + +### Downloading Package Dependencies + +- Give me everything within `/foo/v1.0.0` + +### Loading content from deep within a giant dataset + +- Give me the nodes for the path `/a/b/c/d/e/f/g` + +### Loading a large video optimizing for playback and seek + +- First, give me the first few data blocks `/data/*` +- Second, give me all of the tree except for leaves `/**/!` +- Third, give me everything else. `/**/*` + +### Looking up an entry in a sharded directory + +Given a directory entry I think *might* exist in a sharded directory, I should be able to specify the speculative hamt path for that item, and get back as much of that path that exists. For example: + +"Give me `/AB/F5/3E/B7/11/C3/B9`" + +And if the item I want is actually just at `/AB/F5/3E`, I should get that back. + +## Other notes + +**Cost to the responder.** The graphsync protocol will require a non-zero additional overhead of CPU and memory. This cost must be very clearly articulated, and accounted for, otherwise we will end up opening ugly DoS vectors + +