The Walrus Upload Relay
Walrus enables dApps to store data from within their end-users' browsers having low to moderate machine specifications (mobile devices, low-powered laptops, and so on). This browser-based scenario is difficult to achieve in practice with an in-browser process that directly communicates with the storage nodes due to the high number of network connections required to upload all slivers to all shards.
The upload relay is a downloadable program that community members, Mysten Labs, and dApp writers themselves can run on internet-facing hosts to facilitate storing blob slivers onto the storage nodes on behalf of the end-users, thus mitigating browser resource consumption and enabling web-based store operations.
Mysten Labs runs 2 publicly-available upload relays. You can find them at the following addresses:
-
Testnet:
https://upload-relay.testnet.walrus.space -
Mainnet:
https://upload-relay.mainnet.walrus.space
Design
At a high level, a client stores a blob using an upload relay as follows:
-
First, the client locally encodes the blob and registers it on Sui.
-
Then, the client sends the blob to the upload relay through an HTTP POST request to the blob-relay endpoint
/v1/blob-upload-relay. -
The upload relay then encodes the blob, sends the slivers to the storage nodes, collects a storage confirmation certificate, and sends it back to the client.
-
Finally, the client uses the confirmation certificate to certify the blob on Sui.
The upload relay does not perform any on-chain operation and only helps clients distribute the slivers of their blobs to storage nodes.
The flow between clients and the upload relay is already implemented in the Walrus CLI, Rust SDK, and TS SDK, and therefore developers do not need to implement it themselves. For completeness, the following sections discuss how the service is implemented, paid for, and used by clients.
Operate the upload relay
The upload relay can be operated in 2 ways:
-
Free service: It accepts simple HTTP POST requests with blobs from clients, and relays them to the storage nodes for free.
-
Paid service for public relays: In this configuration, the upload relay requires a tip to relay a blob. The tip can be used by the relay operator to cover the costs of running the infrastructure and turn a profit on the service. At the moment, a constant tip and a tip that is linear in the unencoded data size are allowed.
Upload relays expose a tip-configuration endpoint /v1/tip-config that returns the tipping configuration. For example:
{
"send_tip": {
"address": "0x1234...",
"kind": {
"const": 105
}
}
}
The configuration above specifies that every store operation requires a tip of 105 MIST (arbitrary value), paid to the set address 0x1234... This configuration is provided even for free upload relays that return "no_tip".
Pay the tip
This step is only necessary if the relay requires a tip.
To pay the tip, the client proceeds as follows:
-
It computes the
blob_digest = SHA256(blob). -
It generates a random
nonce, and hashes itnonce_digest = SHA256(nonce). -
It computes the
unencoded_length = blob.len().
Then, it creates a programmable transaction block (PTB), where the first input 0 is the bcs encoded representation of blob_digest || nonce_digest || unencoded_length. This is later used by the relay to authenticate the sender of the store request. In the same PTB, the client transfers the appropriate tip amount to the relay's wallet . This can also be found through the /v1/tip-config endpoint. Usually, the client also registers the blob in this transaction. This is not mandatory, and the blob can be registered in another transaction, but it is commonly convenient and cheaper to perform all these operations together.
After the transaction is executed, the client keeps the transaction ID tx_id, the nonce, and the blob_id, which are required for the next phase.
The relay enforces a freshness check on the transaction that paid the tip, currently 1 hour by default, but each relay can independently configure this.
Send data to the upload relay
See the full OpenAPI spec for the upload relay for the full details.
Essentially, the client sends a POST request to the /v1/blob-upload-relay API endpoint on the relay, containing the bytes of the blob to be stored in the body.
These additional parameters have to be specified in the URL's query string:
-
blob_id: Required. The blob ID of the blob to be stored. Example:blob_id=E7_nNXvFU_3qZVu3OH1yycRG7LZlyn1-UxEDCDDqGGU -
tx_id*: Required if the relay requires a tip. The transaction ID (base58 encoded) of the transaction that transferred thetipto the relay. Example:tx_id=EmcFpdozbobqH61w76T4UehhC4UGaAv32uZpv6c4CNyg -
nonce: Required if the relay requires a tip. Thenonce, the pre-image of the hash added to the transaction inputs created above, as a base64 URL-encoded string without padding. Example:nonce=rw8xIuqxwMpdOcF_3jOprsD9TtPWfXK97tT_lWr1teQ -
deletable_blob_object: Required if the blob is registered as deletable. If the blob being stored is deletable, then the client must specify the object ID as a hexadecimal string of the blob. If the blob is to be stored as a permanent one, this parameter should not be specified. Example:deletable_blob_object=0x56ae1c86e17db174ea002f8340e28880bc8a8587c56e8604a4fa6b1170b23a60 -
encoding_type: Optional. The encoding type to be used for the blob. The default value, and at the moment the only value, isRS2.
Receive the certificate
After the relay is done storing the blob, it collects the confirmation certificate from the storage nodes.
Then, it sends a response to the client, containing the blob_id of the stored blob along with the confirmation_certificate. The client can then use this certificate to certify the blob on chain.
Install the relay
If you'd like to download a pre-built binary to manually run walrus-upload-relay, you need to download it from the releases page. walrus-upload-relay does not daemonize itself, and requires some supervisor process to ensure boot at startup, to restart in the event of failures, and so on.
Use Docker
The docker image for walrus-upload-relay is available on Docker Hub as mysten/walrus-upload-relay.
$ docker run -it --rm mysten/walrus-upload-relay --help
Build from source
The sources for the walrus-upload-relay are available on GitHub in the crates/walrus-upload-relay subdirectory. Run the following from the root of the Walrus repo:
$ cargo build --release --bin walrus-upload-relay
./target/release/walrus-upload-relay --help
Configure the relay
Below is an example of how you might place the configuration such that it is reachable when invoking Docker to run the relay. For the sake of the example below, the following assumptions are made:
-
$HOME/.config/walrus/walrus_upload_relay_config.yamlexists on the host machine and contains the configuration for thewalrus-upload-relay, as described in the configuration section. -
$HOME/.config/walrus/client_config.yamlexists on the host machine and contains Walrus client configuration as specified in the CLI setup section. -
Port 3000 is available for the relay to bind to. Change this to whichever port you'd like to expose from your host.
$ docker run \
-p 3000:3000 \
-v $HOME/.config/walrus/walrus_upload_relay_config.yaml:/opt/walrus/walrus_upload_relay_config.yaml \
-v $HOME/.config/walrus/client_config.yaml:/opt/walrus/client_config.yaml \
mysten/walrus-upload-relay \
--context testnet \
--walrus-config /opt/walrus/client_config.yaml \
--server-address 0.0.0.0:3000 \
--relay-config /opt/walrus/walrus_upload_relay_config.yaml
Configure relay-specific settings
Here is an example of the walrus-upload-relay configuration file:
tip_config: !send_tip
address: 0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a
kind: !const 42
tx_freshness_threshold_secs: 36000
tx_max_future_threshold:
secs: 30
nanos: 0
Currently, the options are the following:
-
tip_config: The configuration for the tip to be paid. It can be!no_tip, for the free service, or!send_tip, to configure the requested tip. In that case,addresscontains the hex-encoded address of the upload relay owner, where the tip should be sent to, andkindis the kind of tip that should be paid. It can be!const, for a constant tip for each store, or!linear, for a tip that is linear in the unencoded blob size. -
tx_freshness_threshold_secs: The maximum amount of time in seconds for which a transaction that pays the tip is considered valid. -
tx_max_future_threshold_secs: The maximum amount of time in the future for which the tip-paying transaction is considered valid. This is simply to account for some clock skew.