Introduction
Freenet is a distributed, decentralized alternative to the centralized World Wide Web, designed to unleash a new era of innovation and competition, while protecting freedom of speech and privacy.
At the core of Freenet is a small piece of software known as the Freenet kernel, which runs on users' computers, smartphones, or other devices. The kernel is tiny, less than 5 MB, allowing it to be installed installed in a matter of seconds and is compatible with a wide range of hardware.
Freenet is a peer-to-peer network, which means that nodes self-organize into a global network without any central authority, and the work of hosting services is distributed among the users.
This user manual is primarily intended for developers who wish to build decentralized software systems on the Freenet platform.
Building Decentralized Applications on Freenet
Delegates, contracts, and user interfaces (UIs) each serve distinct roles in the Freenet ecosystem. Contracts control public data, or "shared state". Delegates act as the user's agent and can store private data on the user's behalf, while UIs provide an interface between these and the user through a web browser. UIs are distributed through the P2P network via contracts.
Freenet Kernel
The kernel is the core of Freenet, it's the software that runs on the user's computer. It's responsible for:
- Providing a user-friendly interface to access Freenet via a web browser
- Host the user's delegates and the private data they store
- Host contracts and their associated data on behalf of the network
- Manage communication between contracts, delegates, and UI components
The kernel is written in Rust and is designed to be small (hopefully less than 5 MB), efficient, and able to run on a wide range of devices like smartphones, desktop computers, and embedded devices. can
User Interface
On the normal web, a user might visit https://gmail.com/, their browser will download the Gmail user interface which then runs in their browser and connects back to the Gmail servers.
On Freenet the user interface is downloaded from a Freenet contract, and it interacts with contracts and delegates through the Freenet kernel.
These UIs are built using web technologies such as HTML, CSS, and JavaScript, and are distributed over Freenet and run in a web browser. UIs can create, retrieve, and update contracts through a WebSocket connection to the local Freenet peer, as well as communicate with delegates.
Because UIs run in a web browser, they can be built using any web framework, such as React, Angular, Vue.js, Bootstrap, and so on.
Contracts
Contracts in Freenet are WebAssembly components that manage and regulate public state. They can be likened to inodes in a filesystem, tables in a database, or memory locations in a globally shared memory. Contracts define the circumstances under which state can be modified and whether a given state is allowed.
Contracts and their associated state reside on the Freenet network on peers determined by the contract's location, which is derived from its WebAssembly code and parameters. While a user's delegates are hosted on their local Freenet peer, contracts are hosted on the network as a whole.
Contracts also outline how to merge two valid states, creating a new state that incorporates both. This process ensures eventual consistency of the state in Freenet, using an approach akin to CRDTs. The contract defines a commutative monoid on the contract's state.
Each contract is identified by a cryptographic hash, which is a combination of its code and parameters, also referred to as its "key". This key is used to identify the contract and to verify that the contract's code and parameters have not been tampered with.
Contract Use Cases
Take, for example, a public blog contract. The state of this contract would be the blog's content, which consists of a list of blog posts. The code within the contract dictates that new posts may only be added if they are signed by the blog's owner, while the contract's parameters include the blog owner's public key.
Delegates
Delegates are WebAssembly code that act as user avatars on Freenet, they must implement the DelegateInterface.
Delegates run in the Freenet kernel and manage private data and interact with other Freenet entities like contracts, apps, and other delegates on behalf of the user. They can store and control private data like cryptographic keys, tokens, and passwords, and communicate with users, for example to ask permission to sign some data. They can be created by UI components or other delegates.
Unlike contracts, which require network verification, delegates run on the user's computer and are trusted to execute code without verification. Delegates' state is private, while contracts' state is public but may be encrypted.
Delegates are used for:
- Managing private data similar to a browser's web
storage
- eg. private keys, tokens
- Acting on the user's behalf on Freenet
- eg. consuming received messages in an inbox
- Storing user data
- e.g., contacts, messages
Origin Attestation
Delegates communicate with apps and other delegates using messages, a crucial aspect of which is the delegate's ability to identify the origin of a message. The origin is identified by the key associated with the app, contract, or other delegate that sent the message. This key is generated cryptographically based on the code and configuration parameters of the sender, enabling delegates to verify the behavior of other delegates or apps with which they interact.
This allows for highly flexible composability, as components can trust the behavior of the components they communicate with by verifying their code and configuration parameters.
Delegate Use Cases
-
A key manager delegate is responsible for managing a user's private keys, other components can request that the key manager delegate sign messages or other data on their behalf.
-
An inbox delegate is responsible for maintaining an inbox of messages sent to the user in a messaging system. It pulls messages from an inbox contract, decrypts them, and stores them locally where they can be queried by other components.
Freenet Network Topology
Small-World Network
Freenet operates as a decentralized peer-to-peer network based on the principles of a small-world network. This network topology allows Freenet to be resilient against denial-of-service attacks, automatically scale to accommodate demand, and provide observable data stores. Users can subscribe to specific keys to receive notifications of updates as they occur.
Understanding Freenet Peers
A Freenet peer refers to a computer that runs the Freenet kernel software and participates in the network. The organization of peers follows a ring structure, where each position on the ring represents a numerical value ranging from 0.0 to 1.0. This value signifies the peer's location within the network.
Establishing Neighbor Connections
Each Freenet peer, or kernel, establishes bi-directional connections with a group of other peers known as its "neighbors." These connections rely on the User Datagram Protocol (UDP) and may involve techniques to traverse firewalls when required.
To optimize resource utilization, peers monitor the resources they use while responding to neighbor requests, including bandwidth, memory, CPU usage, and storage. Peers also track the services offered by their neighbors, measured by the number of requests directed to those neighbors.
To ensure network efficiency, a peer may sever its connection with a neighbor that consumes excessive resources relative to the number of requests it receives.
Implementing Adaptive Routing for Efficient Data Retrieval
When a peer intends to read, create, or modify a contract, it sends a request to the peers hosting the contract. The request is directed to the neighbor most likely to retrieve the contract quickly. Ideally, this neighbor is the one closest to the contract's location, a concept known as "greedy routing." However, other factors, such as connection speed, may influence the selection.
Freenet addresses this challenge by monitoring the past performance of peers and selecting the one most likely to respond quickly and successfully. This selection considers both past performance and proximity to the desired contract. The process, known as adaptive routing, employs an algorithm called isotonic regression.
Intelligent Routing
Freenet's request routing mechanism plays a crucial role in the efficiency of the network.
It is responsible for deciding which peer to route a request to when attempting to read, create, or modify a contract's state. The mechanism is designed to select the peer that can complete the request the fastest, which may not always be the peer closest to the contract's location - the traditional approach for routing in a small-world network, known as greedy routing.
Isotonic Regression
Freenet uses isotonic regression, a method for estimating a monotonically increasing or decreasing function given a set of data, to predict the response time from a peer based on its ring distance from the target location of the request.
This estimation is then adjusted by the average difference between the isotonic regression estimate and the actual response time from previous interactions with the peer. This process enables a form of adaptive routing that selects the peer with the lowest estimated response time.
Router Initialization and Event Handling
When a new Router is created, it's initialized with a history of routing events. These events are processed to generate the initial state of the isotonic estimators. For example, failure outcomes and success durations are computed for each event in the history and used to initialize the respective estimators. The average transfer size is also computed from the history.
The Router can add new events to its history, updating its estimators in the process. When a successful routing event occurs, the Router updates its response start time estimator, failure estimator, and transfer rate estimator based on the details of the event. If a failure occurs, only the failure estimator is updated.
Peer Selection
To select a peer for routing a request, the Router first checks whether it has sufficient historical data. If not, it selects the peer with the minimum distance to the contract location. If it does have sufficient data, it predicts the outcome of routing the request to each available peer and selects the one with the best predicted outcome.
Outcome Prediction
To predict the outcome of routing a request to a specific peer, the Router uses its isotonic estimators to predict the time to the start of the response, the chance of failure, and the transfer rate. These predictions are used to compute an expected total time for the request, with the cost of a failure being assumed as a multiple of the cost of success. The peer with the lowest expected total time is selected for routing the request.
Getting Started
This tutorial will show you how to build decentralized software on Freenet.
- Prerequisites
- Creating a new contract
- Making a container contract
- Writing the backend for our web application
- Testing out contracts in the local node
- Limitations
Prerequisites
Rust and Cargo
This will install a Rust development environment including cargo on Linux or a Mac (for Windows see here):
curl https://sh.rustup.rs -sSf | sh
Locutus Dev Tool (LTD)
Once you have a working installation of Cargo you can install the Locutus dev tools:
cargo install locutus
This command will install ldt
(Locutus Dev Tool) and a working Freenet kernel that can
be used for local development.
Node.js and TypeScript
To build user interfaces in JavaScript or TypeScript, you need to have Node.js and npm installed. On Linux or Mac:
sudo apt update
sudo apt install nodejs npm
For Windows, you can download Node.js and npm from here.
Once Node.js and npm are installed, you can install TypeScript globally on your
system, which includes the tsc
command:
sudo npm install -g typescript
You can verify the installation by checking the version of tsc
:
tsc --version
This command should output the version of TypeScript that you installed.
Creating a new contract
You can create a new contract skeleton by executing the
new
command with ldt
. Two contract types are supported currently by the
tool, regular contracts, and web
application container
contracts. Currently, the following
technological stacks are supported (more to be added in the future):
- Regular contracts:
- Rust (default)
- Web applications:
- Container development:
- Rust (default)
- Web/state development:
- Typescript. (default: using npm and webpack)
- JavaScript.
- Rust (WIP).
- Container development:
We will need to create a directory that will hold our web app and initialize it:
mkdir -p my-app/web
mkdir -p my-app/backend
cd my-app/web
ldt new web-app
will create the skeleton for a web application and its container contract for
Locutus ready for development at the my-app/web
directory.
Making a container contract
The first thing that we need is to write the code for our container contract. This contract's role is to contain the web application code itself, allowing it to be distributed over Locutus.
The new
command has created the source ready to be modified for us, in your
favorite editor open the following file:
./container/src/lib.rs
In this case, and for simplicity's sake, the contract won't be performing any functions, but in a realistic scenario, this contract would include some basic security functionality like verifying that whoever is trying to update the contract has the required credentials.
To make our contract unique so it doesn't collide with an existing contract, we can generate a random signature that will be embedded with the contract.
For example in the lib.rs
file we will write the following:
use locutus_stdlib::prelude::*;
pub const RANDOM_SIGNATURE: &[u8] = &[6, 8, 2, 5, 6, 9, 9, 10];
struct Contract;
#[contract]
impl ContractInterface for Contract {
fn validate_state(
_parameters: Parameters<'static>,
_state: State<'static>,
_related: RelatedContracts<'static>,
) -> Result<ValidateResult, ContractError> {
unimplemented!()
}
fn validate_delta(
_parameters: Parameters<'static>,
_delta: StateDelta<'static>,
) -> Result<bool, ContractError> {
unimplemented!()
}
fn update_state(
_parameters: Parameters<'static>,
_state: State<'static>,
_data: Vec<UpdateData<'static>>,
) -> Result<UpdateModification<'static>, ContractError> {
unimplemented!()
}
fn summarize_state(
_parameters: Parameters<'static>,
_state: State<'static>,
) -> Result<StateSummary<'static>, ContractError> {
unimplemented!()
}
fn get_state_delta(
_parameters: Parameters<'static>,
_state: State<'static>,
_summary: StateSummary<'static>,
) -> Result<StateDelta<'static>, ContractError> {
unimplemented!()
}
}
That's a lot of information, let's unpack it:
use locutus_stdlib::prelude::*;
Here we are importing the necessary types and traits to write a Locutus contract successfully using Rust.
pub const RANDOM_SIGNATURE: &[u8] = &[6, 8, 2, 5, 6, 9, 9, 10];
This will make our contract unique, notice the pub
qualifier so the compiler
doesn't remove this constant because is unused and is included in the output of
the compiler.
struct Contract;
#[contract]
impl ContractInterface for Contract {
...
}
Here we create a new type, Contract
for which we will be implementing the
ContractInterface
trait. To know more details about the functionality of a
contract, delve into the details of the contract
interface.
Notice the #[contract]
macro call, this will generate the necessary code for
the WASM runtime to interact with your contract ergonomically and safely. Trying
to use this macro more than once in the same module will result in a compiler
error, and only the code generated at the top-level module will be used by the
runtime.
As a rule of thumb, one contract will require implementing the `ContractInterface`` exactly once.
Creating a web application
Now we have a working example of a contract, but our contract is an empty shell, which does not do anything yet. To change this, we will start developing our web application.
To do that, we can go and modify the code of the contract state, which in this
case is the web application. Locutus offers a standard library (stdlib) that can
be used with Typescript/JavaScript to facilitate the development of web
applications and interfacing with your local node, so we will make our
package.json
contains the dependency:
{
"dependencies": {
"@locutus/locutus-stdlib": "0.0.2"
}
}
Open the file src/index.ts
in a code editor and you can start developing the
web application.
An important thing to notice is that our application will need to interface with our local node, the entry point for our machine to communicate with other nodes in the network. The stdlib offers a series of facilities in which you will be able to communicate with the network ergonomically.
Here is an example of how you could write your application to interact with the node:
import {
GetResponse,
HostError,
Key,
LocutusWsApi,
PutResponse,
UpdateNotification,
UpdateResponse,
} from "@locutus/locutus-stdlib/webSocketInterface";
const handler = {
onPut: (_response: PutResponse) => {},
onGet: (_response: GetResponse) => {},
onUpdate: (_up: UpdateResponse) => {},
onUpdateNotification: (_notif: UpdateNotification) => {},
onErr: (err: HostError) => {},
onOpen: () => {},
};
const API_URL = new URL(`ws://${location.host}/contract/command/`);
const locutusApi = new LocutusWsApi(API_URL, handler);
const CONTRACT = "DCBi7HNZC3QUZRiZLFZDiEduv5KHgZfgBk8WwTiheGq1";
async function loadState() {
let getRequest = {
key: Key.fromSpec(CONTRACT),
fetch_contract: false,
};
await locutusApi.get(getRequest);
}
Let's unpack this code:
const handler = {
onPut: (_response: PutResponse) => {},
onGet: (_response: GetResponse) => {},
onUpdate: (_up: UpdateResponse) => {},
onUpdateNotification: (_notif: UpdateNotification) => {},
onErr: (err: HostError) => {},
onOpen: () => {},
};
const API_URL = new URL(`ws://${location.host}/contract/command/`);
const locutusApi = new LocutusWsApi(API_URL, handler);
This type provides a convenient interface to the WebSocket API. It receives an object which handles the different responses from the node via callbacks. Here you would be able to interact with DOM objects or other parts of your code.
const CONTRACT = "DCBi7HNZC3QUZRiZLFZDiEduv5KHgZfgBk8WwTiheGq1";
async function loadState() {
let getRequest = {
key: Key.fromSpec(CONTRACT),
fetch_contract: false,
};
await locutusApi.get(getRequest);
}
Here we use the API wrapper to make a get request (which requires a key and
specifies if we require fetching the contract code or not) to get the state for
a contract with the given address. The response from the node will be directed
to the onGet
callback. You can use any other methods available in the API to
interact with the node.
Writing the backend for our web application
In the creating a new contract section we described the contract interface, but we were using it to write a simple container contract that won't be doing anything in practice, just carrying around the front end of your application. The core logic of the application, and a back end where we will be storing all the information, requires another contract. So we will create a new contract in a different directory for it:
cd ../backend
ldt new contract
This will create a regular contract, and we will need to implement the interface on a type that will handle our contract code. For example:
use locutus_stdlib::prelude::*;
pub const RANDOM_SIGNATURE: &[u8] = &[6, 8, 2, 5, 6, 9, 9, 10];
struct Contract;
struct Posts(...)
impl Posts {
fn add_post(&mut self, post: Post) { ... }
}
struct Post(...)
#[contract]
impl ContractInterface for Contract {
fn update_state(
_parameters: Parameters<'static>,
state: State<'static>,
data: Vec<UpdateData<'static>>,
) -> Result<UpdateModification<'static>, ContractError> {
let mut posts: Posts = serde_json::from_slice(&state).map_err(|_| ContractError::InvalidState)?;
if let Some(UpdateData::Delta(delta)) = data.pop() {
let new_post: Posts = serde_json::from_slice(&delta).map_err(|_| ContractError::InvalidState);
posts.add_post(new_post)?;
} else {
Err(ContractError::InvalidUpdate)
}
Ok(UpdateModification::valid(posts.into()))
}
...
}
In this simple example, we convert a new incoming delta to a post and the state to a list of posts we maintain, and we append the post to the list of posts. After that, we convert back the posts list to an state and return that.
If we subscribe to the contract changes or our web app, we will receive a notification with the updates after they are successful, and we will be able to render them in our browser. We can do that, for example, using the API:
function getUpdateNotification(notification: UpdateNotification) {
let decoder = new TextDecoder("utf8");
let updatesBox = document.getElementById("updates") as HTMLPreElement;
let newUpdate = decoder.decode(Uint8Array.from(notification.update));
let newUpdateJson = JSON.parse(newUpdate);
updatesBox.textContent = updatesBox.textContent + newUpdateJson;
}
Building and packaging a contract
Now that we have the front end and the back end of our web app, we can package the contracts and run them in the node to test them out.
In order to do that, we can again use the development tool to help us out with
the process. But before doing that, let's take a look at the manifesto format
and understand the different parameters that allow us to specify how this
contract should be compiled (check the manifest details for
more information). In the web app directory, we have a locutus.toml
file which
contains something similar to:
[contract]
type = "webapp"
lang = "rust"
...
[webapp.state-sources]
source_dirs = ["dist"]
This means that the dist
directory will be packaged as the initial state for
the webapp (that is the code the browser will be interpreting and in the end,
rendering).
If we add the following keys to the manifesto:
[webapp.dependencies]
posts = { path = "../backend" }
The WASM code from the backend
contract will be embedded in our web
application state, so it will be accessible as a resource just via the local
HTTP gateway access and then we can re-use it for publishing additional
contracts.
Currently, wep applications follow a standarized build procedure in case you use
ldt
and assumptions about your system. For example, in the case of a type = "webapp"
contract, if nothing is specified, it will assume you have npm
and
the tsc
compiler available at the directory level, as well as webpack
installed.
This means that you have installed either globally or at the directory level, e.g. globally:
npm install -g typescript webpack webpack-cli
or locally (make sure your package.json
file has the required dependencies):
npm install --save-dev typescript webpack webpack-cli
If, however, you prefer to follow a different workflow, you can write your own by enabling/disabling certain parameters or using a blank template. For example:
[contract]
lang = "rust"
[state]
files = ["my_packaged_web.tar.xz"]
Would just delegate the work of building the packaged tar
to the developer.
Or:
[contract]
type = "webapp"
lang = "rust"
[webapp]
lang = "typescript"
[webapp.typescript]
webpack = false
would disable using webpack
at all.
Now that we understand the details, and after making any necessary changes, in each contract directory we run the following commands:
ldt build
This command will read your contract manifest file (locutus.toml
) and take
care of building the contract and packaging it, ready for the node and the
network to consume it.
Under the ./build/locutus
directory, you will see both a *.wasm
file, which
is the contract file, and contract-state
, in case it applies, which is the
initial state that will be uploaded when initially putting the contract.
Web applications can access the code of backend contracts directly in their applications and put new contracts (that is, assigning a new location for the code, plus any parameters that may be generated dynamically by the web app, and the initial state for that combination of contract code + parameters) dynamically.
Let's take a look at the manifest for our web app container contract:
Testing out contracts in the local node
Once we have all our contracts sorted and ready for testing, we can do this in local mode in our node. For this the node must be running, we can make sure that is running by running the following command as a background process or in another terminal; since we have installed it:
locutus-node
You should see some logs printed via the stdout of the process indicating that the node HTTP gateway is running.
Once the HTTP gateway is running, we are ready to publish the contracts to our local Locutus node:
cd ../backend && ldt publish --code="./build/locutus/backend.wasm" --state="./build/locutus/contract-state"
cd ../web && ldt publish --code="./build/locutus/web.wasm" --state="./build/locutus/contract-state"
In this case, we're not passing any parameters (so our parameters will be an empty byte array), and we are passing an initial state without the current backend contract. In typical use, both the parameters would have meaningful data, and the backend contract may be dynamically generated from the app and published from there.
Once this is done, you can start your app just by pointing to it in the browser:
http://127.0.0.1:50509/contract/web/<CONTRACT KEY>
For example
http://127.0.0.1:50509/contract/web/CYXGxQGSmcd5xHRJNQygPwmUJsWS2njh3pdVjfVz9EV/
Iteratively you can repeat this process of modifying, and publishing locally until you are confident with the results and ready to publish your application.
Since the web is part of your state, you are always able to update it, pointing to new contracts, and evolving it over time.
Limitations
-
Publishing to the Locutus network is not yet supported.
-
Only Rust is currently supported for contract development, but we'll support more languages like AssemblyScript in the future.
-
Binaries for all the required tools are not yet available, they must be compiled from source
Contract Interface
Terms
- Contract State - data associated with a contract that can be retrieved by Applications and Delegates.
- Delta - Represents a modification to some state - similar to a diff in source code
- Parameters - Data that forms part of a contract along with the WebAssembly code
- State Summary - A compact summary of a contract's state that can be used to create a delta
Interface
Locutus contracts must implement the contract interface from crates/locutus-stdlib/src/contract_interface.rs:
/// # ContractInterface
///
/// This trait defines the core functionality for managing and updating a contract's state.
/// Implementations must ensure that state delta updates are *commutative*. In other words,
/// when applying multiple delta updates to a state, the order in which these updates are
/// applied should not affect the final state. Once all deltas are applied, the resulting
/// state should be the same, regardless of the order in which the deltas were applied.
///
/// Noncompliant behavior, such as failing to obey the commutativity rule, may result
/// in the contract being deprioritized or removed from the p2p network.
pub trait ContractInterface {
/// Verify that the state is valid, given the parameters.
fn validate_state(
parameters: Parameters<'static>,
state: State<'static>,
related: RelatedContracts<'static>,
) -> Result<ValidateResult, ContractError>;
/// Verify that a delta is valid if possible, returns false if and only delta is
/// definitely invalid, true otherwise.
fn validate_delta(
parameters: Parameters<'static>,
delta: StateDelta<'static>,
) -> Result<bool, ContractError>;
/// Update the state to account for the new data
fn update_state(
parameters: Parameters<'static>,
state: State<'static>,
data: Vec<UpdateData<'static>>,
) -> Result<UpdateModification<'static>, ContractError>;
/// Generate a concise summary of a state that can be used to create deltas
/// relative to this state.
fn summarize_state(
parameters: Parameters<'static>,
state: State<'static>,
) -> Result<StateSummary<'static>, ContractError>;
/// Generate a state delta using a summary from the current state.
/// This along with [`Self::summarize_state`] allows flexible and efficient
/// state synchronization between peers.
fn get_state_delta(
parameters: Parameters<'static>,
state: State<'static>,
summary: StateSummary<'static>,
) -> Result<StateDelta<'static>, ContractError>;
}
Parameters
, State
, and StateDelta
are all wrappers around simple [u8]
byte arrays for maximum efficiency and flexibility.
Contract Interaction
In the (hopefully) near future we'll be adding the ability for contracts to read each other's state while validating and updating their own, see issue #167 for the latest on this.
The Manifest Format
The locutus.toml
file for each UI component/contract is called its manifest.
It is written in the TOML format. Manifest files consist of
the following sections:
- [contract] — Defines a contract.
- type — Contract type.
- lang — Contract source language.
- output_dir — Output path for build artifacts.
- [webapp] — Configuration for UI component containers.
- [state] — Optionally seed a state.
The [contract]
section
The type
field
[contract]
...
type = "webapp"
The type of the contract being packaged. Currently the following types are supported:
standard
, the default type, it can be ellided. This is just a standard contract.webapp
, a web app container contract. Additionally to the container contract the UI component source will be compiled and packaged as the state of the contract.
The lang
field
[contract]
...
lang = "rust"
The programming language in which the contract is written. If specified the build tool will compile the contract. Currently only Rust is supported.
The output_dir
field
[contract]
...
output_dir = "./other/output/dir/"
An optional path to the output directory for the build artifacts. If not set the
output will be written to the relative directory ./build/locutus
from the
manifest file directory.
The [webapp]
section
An optional section, only specified in case of webapp
contracts.
The lang
field
[webapp]
...
lang = "typescript"
The programming language in which the web application is written. Currently the following languages are supported:
The metadata
field
[webapp]
...
metadata = "/path/to/metadata/file"
An optional path to the metadata for the webapp, if not set the metadata will be empty.
The [webapp.typescript]
options section
Optional section specified in case of the the typescript
lang.
The following fields are supported:
[webapp.typescript]
webpack = true
webpack
— if set webpack will be used when packaging the contract state.
The [webapp.javascript]
options section
Optional section specified in case of the the javascript
lang.
The following fields are supported:
[webapp.javascript]
webpack = true
webpack
— if set webpack will be used when packaging the contract state.
The [webapp.state-sources]
options section
[webapp.state-sources]
source_dirs = ["path/to/sources"]
files = ["*/src/**.js"]
Specifies the sources for the state of the contract, this will be later on
unpacked and accessible at the HTTP gateway from the Locutus node. Includes any
web sources (like .html or .js files). The source_dirs
field is a comma
separated array of directories that should be appended to the root of the state,
the files
field is a comma separated array of
glob compatible patterns
to files that will be appendeded to the state.
At least one of source_dirs
or files
fields are required.
The [webapp.dependencies]
section
[webapp.dependencies]
...
posts = { path = "../contracts/posts" }
An optional list of contract dependencies that will be embedded and available in
the state of the contract. Each entry under this entry represents an alias to
the contract code, it must include a path
field that specifies the relative
location of the dependency from this manifesto directory.
If dependencies are specified they will be compiled and appended to the contract
state, under the contracts
directory, and as such, become available from the
HTTP gateway. A dependencies.json
file will be automatically generated and
placed under such directory that maps the aliases to the file and hash of the
code generated for the dependencies.
In this way the "parent" container contract can use those contracts code to put/update new values through the websocket API in an ergonomic manner.
The [state]
section
[state]
files = ["*/src/**.js"]
An optional section for standard contracts in case they want to seed an state initially, it will take a single file and make it available at the build directory.
Example: Antiflood Token System
Purpose
The Antiflood Token System (AFT) is a decentralized system aimed to provide a simple, but general purpose solution to flooding, denial-of-service attacks, and spam.
AFT allows users to generate tokens through a "token generator", which is created by completing a "hard" task, such as making a donation to Freenet. Tokens are generated at a fixed rate and can be utilized to perform activities, such as sending messages.
The recipient can specify the required token "tier," with each tier being generated at different intervals (e.g. 1 minute, 1 hour). This way, if a recipient experiences a high volume of messages, they can increase the token tier to make it more challenging to generate, thus reducing the flood.
AFT Delegate
The AFT relies on a TokenDelegate that implements this DelegateInterface.
Token Generator
The TokenAllocContract keeps track of token assignments to ensure that tokens are not double spent. New tokens are generated at a fixed rate that depends on the tier required by the recipient.
Recepient Inbox
The recipient inbox contract keeps track of inbound messages sent to a recipient, verifying that each is accompanied by a valid token of the required tier.
Sequence Diagram
sequenceDiagram participant User participant Application participant Delegate participant TokenGeneratorContract participant RecipientInboxContract User->>Application: 1. RequestToken Application->>Delegate: 2. RequestToken Delegate->>User: 3. Allocate? User->>Delegate: 4. approved Delegate->>TokenGeneratorContract: 5. TokenAllocation Delegate->>RecipientInboxContract: 6. Message+TokenAllocation RecipientInboxContract->>TokenGeneratorContract: 7. verify TokenGeneratorContract->>RecipientInboxContract: 8. verified
-
User requests a token from the application, perhaps by composing a message and clicking "send" in the UI
-
The application requests a token from the delegate via its websocket connection to the Freenet node
-
The delegate requests permission from the user to allocate a token, this occurs independently of the application, perhaps via an OS-specific notification mechanism
-
The user approves the allocation
-
The delegate allocates a token to the token generator contract
-
The delegate sends the message and token allocation to the recipient inbox contract
-
The recipient inbox contract verifies that the token allocation is valid before appending the inbound message to its state
-
The token generator contract verifies that the token allocation is valid and adds it to its list of allocations
Blind Attestations
Overview
A mechanism to attest the owner of a "target" contract performed some action, while preserving the owner's anonymity using a blind signature.
A typical use would be for the Freenet non-profit to attest that the owner of a particular contract made a donation to project. The owner can then use this attestation to prove they made a donation, without revealing their identity to Freenet or anyone else.
This contract could then be thought to have a value of the donation amount, this could then serve as collateral to secure a transaction with a counterparty, such as a purchase or a loan.
To do this, the contract would allow the contract owner to temporarily give the counterparty the ability to "disable" the contract for a mutually agreed period of time. The parties then conduct their transaction. If the counterparty is dissatisfied with the transaction then they can disable the contract as punishment, during which time it cannot be used.
Attestation
#![allow(unused)] fn main() { let contract_key = // The contract which we want Freenet to attest to let (blinded_attestation_request, blind_key) = BlindAttestationRequest::blind( &mut rng, &contract_key, ); }
The contract owner then sends the blinded attestation request to Freenet:
#![allow(unused)] fn main() { // URL is https://freenet.org/attestation?blinded_contract_key=4F6oPq... open_in_browser(&blinded_attestation_request.to_url()); }
The user then follows the instructions on freenet.org to complete the donation. Once the donation is complete, freenet.org signs the blinded_contract_key and sends the attestation response through a response contract in Freenet. This may also be sent via a browser redirect to the application.
The attestation consists of:
#![allow(unused)] fn main() { struct Attestation { pub signature : Signature, pub authorization : Authorization, pub authorization_sig : Signature, /// fn is_valid(&self) -> Result<Authorization, String> { if (!signature.verify(&authorization.pubkey, &self.target)) { return Err("The target's signature is invalid"); } if (!authorization_sig.verify(&freenet_public_key, &self.authorization)) { return Err("The authorization's signature is invalid"); } Ok(self.authorization) } } enum Authorization { FreenetDonation(pubkey : PublicKey, amount_range : (Money, Money), time_range : (Timestamp, Timestamp)), } enum Target { Contract(ContractKey), } }
Community
Frequently Asked Questions
What is Freenet?
Freenet is a distributed and decentralized alternative to the centralized World Wide Web, designed to foster innovation, competition, and protect freedom of speech and privacy. As a global decentralized computer, it allows anyone to contribute to its growth and development. This unique platform aims to counteract the centralization of the web, which has hindered progress and jeopardized individual liberties. Furthermore, Freenet is a resilient communication network that can operate on today's internet infrastructure as well as adapt to future satellite or mesh networks.
Glossary
Application
Software that uses Locutus as a back-end. This includes native software distributed independenly of Locutus but which uses Locutus as a back-end (perhaps bundling Locutus), and web applications that are distributed over Locutus and run in a web browser.
Contract
A contract is WebAssembly code with associated data like the contract state. The role of the contract is to determine:
- Is the state valid for this contract?
- Under what circumstances can the state be modified or updated? (see Delta)
- How can two valid states be merged to produce a third valid state?
Container Contract
A contract that contains an application or component as state, accessed through the web proxy.
For example, if the contract id is
6C2KyVMtqw8D5wWa8Y7e14VmDNXXXv9CQ3m44PC9YbD2
then visiting
http://localhost:PORT/contract/web/6C2KyVMtqw8D5wWa8Y7e14VmDNXXXv9CQ3m44PC9YbD2
will cause the application/component to be retrieved from Locutus, decompressed,
and sent to the browser where it can execute.
Contract State
Data associated with a contract that can be retrieved by Applications and
Components. For efficiency and flexibility, contract state is represented as a
simple [u8]
byte array.
Delegate
A delegate is a piece of software that runs on the user's computer and acts on the user's behalf. Similar to local storage in a web browser, delegates can store private data on the user's computer and control how it is used. Delegates can also interact with contracts, applications, and other delegates.
Delta
Represents a modification to some state - similar to a diff in source code. The exact format of a delta is determined by the contract. A contract will determine whether a delta is valid - perhaps by verifying it is signed by someone authorized to modify the contract state. A delta may be created in response to a State Summary as part of the State Synchronization mechanism.
Parameters
Data that forms part of a contract along with the WebAssembly code. This is supplied to the contract as a parameter to the contract's functions. Parameters are typically be used to configure a contract, much like the parameters of a constructor function.
For example, the parameters could contain a hash of the state itself. The contract would then use it to verify that the state hashes to that value. This would create a contract that is guaranteed to contain the same state. In the original Freenet, this was known as a content hash key.
State Summary
Given a contract state, this is a small piece of data that can be used to determine a delta between two contracts as part of the state synchronization mechanism. The format of a state summary is determined by the state's contract.
State Synchronization
Given two valid states for a contract, the state synchronization mechanism allows the states to be efficiently merged over the network to ensure eventual consistency.
Web Application
Software built on Locutus and distributed through Locutus.
Applications run in the browser and can be built with tools like React, TypeScript, and Vue.js. An application may use multiple components and contracts.
Applications are compressed and distributed via a container contract.