Autobase
A multiwriter data structure for combining multiple writer cores into a view of the system. Using the event sourcing pattern, writers append blocks which are linearized into an eventually consistent order for building a view of the system, combining their inputs.
Install
npm i autobase
Usage
Ordering
Autobase writer nodes explicitly reference previous nodes creating a causal directed acyclic graph (DAG). The nodes are linearized by analyzing the causal references so:
Nodes never precede nodes they reference.
Ordering is eventually consistent.
Reordering
As new causal information comes in, existing nodes may be reordered when causal forks occur. Any changes to the view will be undone and reapplied on top of the new ordering.
Signed Length
The linearizing algorithm is able to define checkpoints after which the ordering of the graph will never change. This point advances continually, so long as a majority set of indexers are writing messages. These checkpoints allow peers who are behind to catchup quickly and reduce the need to reorder nodes.
Views
A view is one or more hypercores whose contents are created by deterministically applying the linearized nodes from writers. The view represents the combined history of all writers' inputs or the current state of the system as a whole.
Autobase accepts an open
function for creating views and an apply
function that can be used to update the views based on the writer nodes.
IMPORTANT: Autobase messages may be reordered as new data becomes available. Updates will be undone and reapplied internally. So it is important that the open
handler returns a data structure only derived from its store
object argument and that while updating the view in the apply
function, the view
argument is the only data structure being update and that its fully deterministic. If any external data structures are used, these updates will not be correctly undone.
API
Autobase
const base = new Autobase(store, bootstrap, opts)
const base = new Autobase(store, bootstrap, opts)
Instantiate an Autobase.
If loading an existing Autobase then set bootstrap
to base.key
, otherwise pass bootstrap
as null or omit.
opts
takes the following options:
An ackInterval
may be set to enable automatic acknowledgements. When enabled, in cases where it would help the linearizer converge, the base shall eagerly append null
values to merge causal forks.
Setting an autobase to be optimistic
means that writers can append an optimistic
block even when they are not a writer. For a block to be optimistically applied to the view, the writer must be acknowledge via host.ackWriter(key)
.
Note: Optimistic blocks should self verify in the apply
handler to prevent unintended writers from appending blocks to exploit the system. If the apply
handler does not have a way to verify optimistic blocks, any writer could append blocks even when not added to the system.
base.key
base.key
The primary key of the autobase.
base.discoveryKey
base.discoveryKey
The discovery key associated with the autobase.
base.isIndexer
base.isIndexer
Whether the instance is an indexer.
base.writable
base.writable
Whether the instance is a writer for the autobase.
base.view
base.view
The view of the autobase derived from writer inputs. The view is created in the open
handler and can have any shape. The most common view
is a hyperbee.
base.length
base.length
The length of the system core. This is neither the length of the local writer nor the length of the view. The system core tracks the autobase as a whole.
base.signedLength
base.signedLength
The index of the system core that has been signed by a quorum of indexers. The system up until this point will not change.
base.paused
base.paused
Returns true
if the autobase is currently paused, otherwise returns false
.
await base.append(value, opts)
await base.append(value, opts)
Append a new entry to the autobase.
Options include:
await base.update()
await base.update()
Fetch all available data and update the linearizer.
await base.ack(bg = false)
await base.ack(bg = false)
Manually acknowledge the current state by appending a null
node that references known head nodes. null
nodes are ignored by the apply
handler and only serve as a way to acknowledge the current linearized state. Only indexers can ack.
If bg
is set to true
, the ack will not be appended immediately but will set the automatic ack timer to trigger as soon as possible.
const hash = await base.hash()
const hash = await base.hash()
Returns the hash of the system core's merkle tree roots.
const stream = base.replicate(isInitiator || stream, opts)
const stream = base.replicate(isInitiator || stream, opts)
Creates a replication stream for replicating the autobase. Arguments are the same as corestores's .replicate()
.
const heads = base.heads()
const heads = base.heads()
Gets the current writer heads. A writer head is a node which has no causal dependents, aka it is the latest write. If there is more than one head, there is a causal fork which is pretty common.
await base.pause()
await base.pause()
Pauses the autobase prevent the next apply from running.
await base.resume()
await base.resume()
Resumes a paused autobase and will check for an update.
const core = Autobase.getLocalCore(store, handlers, encryptionKey)
const core = Autobase.getLocalCore(store, handlers, encryptionKey)
Generate a local core to be used for an Autobase.
handlers
are any options passed to store
to get the core.
const { referrer, view } = Autobase.getUserData(core)
const { referrer, view } = Autobase.getUserData(core)
Get user data associated with an autobase core
. referrer
is the .key
of the autobase the core
is from. view
is the name
of the view.
const isBase = Autobase.isAutobase(core, opts)
const isBase = Autobase.isAutobase(core, opts)
Returns whether the core is an autobase core. opts
are the same options as core.get(index, opts).
base.on('update', () => { ... })
base.on('update', () => { ... })
Triggered when the autobase view updates after apply
has finished running.
base.on('interrupt', (reason) => { ... })
base.on('interrupt', (reason) => { ... })
Triggered when host.interrupt(reason)
is called in the apply
handler. See host.interrupt(reason)
for when interrupts are used.
base.on('fast-forward', (to, from) => { ... })
base.on('fast-forward', (to, from) => { ... })
Triggered when the autobase fast forwards to a state already with a quorum. to
and from
are the .signedLength
after and before the fast forward respectively.
Fast forwarding speeds up an autobase catching up to peers.
base.on('is-indexer', () => { ... })
base.on('is-indexer', () => { ... })
Triggered when the autobase instance is an indexer.
base.on('is-non-indexer', () => { ... })
base.on('is-non-indexer', () => { ... })
Triggered when the autobase instance is not an indexer.
base.on('writable', () => { ... })
base.on('writable', () => { ... })
Triggered when the autobase instance is now a writer.
base.on('unwritable', () => { ... })
base.on('unwritable', () => { ... })
Triggered when the autobase instance is no longer a writer.
base.on('warning', (warning) => { ... })
base.on('warning', (warning) => { ... })
Triggered when a warning is triggered.
base.on('error', (err) => { ... })
base.on('error', (err) => { ... })
Triggered when an error is triggered while updating the autobase.
AutoStore
AutoStore
Each autobase creates a AutoStore
which is used to create views. The store is passed to the open
function.
const core = store.get(name || { name, valueEncoding })
const core = store.get(name || { name, valueEncoding })
Load a Hypercore
by name (passed as name
). name
should be passed as a string.
AutobaseHostCalls
AutobaseHostCalls
An instance of this is passed to apply
and can be used to invoke the following side effects on the base itself.
await host.addWriter(key, { indexer = true })
await host.addWriter(key, { indexer = true })
Add a writer with the given key
to the autobase allowing their local core to append. If indexer
is true
, it will be added as an indexer.
await host.removeWriter(key)
await host.removeWriter(key)
Remove a writer from the autobase. This will throw if the writer cannot be removed.
await host.ackWriter(key)
await host.ackWriter(key)
Acknowledge a writer even if they haven't been added before. This is most useful for applying optimistic
blocks from writers that are not currently a writer.
host.interrupt(reason)
host.interrupt(reason)
Interrupt the applying of writer blocks optionally giving a reason
. This will emit an interrupt
event passing the reason
to the callback and close the autobase.
Interrupts are an escape hatch to stop the apply function and resolve the issue by updating your apply function to handle it. A common scenario is adding a new block type that an older peer gets from a newer peer.
host.removeable(key)
host.removeable(key)
Returns whether the writer for the given key
can be removed. The last indexer cannot be removed.
Last updated