Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
3cdc45ee0f | |||
bef511363e | |||
fb75cdce03 | |||
13774a8aee | |||
4d66995fee | |||
a0a14cc11c | |||
8afb316399 | |||
f2a12b38c4 | |||
f828da674d | |||
9fa6d29c88 | |||
d589af14a8 | |||
f57d4f0802 | |||
604960938b | |||
0b724bd4d5 | |||
21adbe2fac | |||
8b37def961 | |||
fce3e45f2b | |||
99db027993 | |||
8a1f6a6c6f | |||
9704ced894 | |||
99a431cf50 |
@ -1,2 +1 @@
|
||||
.github
|
||||
.git
|
||||
|
14
.readthedocs.yml
Normal file
@ -0,0 +1,14 @@
|
||||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
version: 2
|
||||
|
||||
sphinx:
|
||||
configuration: docs/swarm-guide/contents/conf.py
|
||||
builder: html
|
||||
|
||||
python:
|
||||
version: 3.7
|
||||
install:
|
||||
- requirements: docs/swarm-guide/requirements.txt
|
@ -4,6 +4,7 @@ sudo: false
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /v(\d+\.)(\d+\.)(\d)/
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
|
24
CHANGELOG.md
@ -1,3 +1,27 @@
|
||||
## v0.4.3 (Unreleased)
|
||||
|
||||
### Notes
|
||||
|
||||
### Features
|
||||
|
||||
### Improvements
|
||||
|
||||
### Bug fixes
|
||||
|
||||
## v0.4.2 (28 June 2019)
|
||||
|
||||
### Notes
|
||||
|
||||
This release is not backward compatible with the previous versions of Swarm due to changes to the wire protocol of the Retrieve Request messages. Please update your nodes.
|
||||
|
||||
### Bug fixes and Improvements
|
||||
|
||||
* [#1503](https://github.com/ethersphere/swarm/pull/1503): network/simulation: add ExecAdapter capability to swarm simulations
|
||||
* [#1495](https://github.com/ethersphere/swarm/pull/1495): build: enable ubuntu ppa disco (19.04) builds
|
||||
* [#1395](https://github.com/ethersphere/swarm/pull/1395): swarm/storage: support for uploading 100gb files
|
||||
* [#1344](https://github.com/ethersphere/swarm/pull/1344): swarm/network, swarm/storage: simplification of fetchers
|
||||
* [#1488](https://github.com/ethersphere/swarm/pull/1488): docker: include git commit hash in swarm version
|
||||
|
||||
## v0.4.1 (June 13, 2019)
|
||||
|
||||
### Improvements
|
||||
|
@ -7,7 +7,7 @@ RUN make swarm
|
||||
FROM ethereum/client-go:v1.8.27 as geth
|
||||
|
||||
FROM alpine:3.9
|
||||
RUN apk --no-cache add ca-certificates
|
||||
RUN apk --no-cache add ca-certificates && update-ca-certificates
|
||||
COPY --from=builder /swarm/build/bin/swarm /usr/local/bin/
|
||||
COPY --from=geth /usr/local/bin/geth /usr/local/bin/
|
||||
COPY docker/run.sh /run.sh
|
||||
|
203
README.md
@ -1,4 +1,4 @@
|
||||
## Swarm
|
||||
## Swarm <!-- omit in toc -->
|
||||
|
||||
[https://swarm.ethereum.org](https://swarm.ethereum.org)
|
||||
|
||||
@ -7,21 +7,28 @@ Swarm is a distributed storage platform and content distribution service, a nati
|
||||
[](https://travis-ci.org/ethersphere/swarm)
|
||||
[](https://gitter.im/ethersphere/orange-lounge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
## Table of Contents
|
||||
## Table of Contents <!-- omit in toc -->
|
||||
|
||||
* [Building the source](#building-the-source)
|
||||
* [Running Swarm](#running-swarm)
|
||||
* [Documentation](#documentation)
|
||||
* [Developers Guide](#developers-guide)
|
||||
* [Go Environment](#development-environment)
|
||||
* [Vendored Dependencies](#vendored-dependencies)
|
||||
* [Testing](#testing)
|
||||
* [Profiling Swarm](#profiling-swarm)
|
||||
* [Metrics and Instrumentation in Swarm](#metrics-and-instrumentation-in-swarm)
|
||||
* [Public Gateways](#public-gateways)
|
||||
* [Swarm Dapps](#swarm-dapps)
|
||||
* [Contributing](#contributing)
|
||||
* [License](#license)
|
||||
- [Building the source](#building-the-source)
|
||||
- [Running Swarm](#running-swarm)
|
||||
- [Verifying that your local Swarm node is running](#verifying-that-your-local-swarm-node-is-running)
|
||||
- [Ethereum Name Service resolution](#ethereum-name-service-resolution)
|
||||
- [Documentation](#documentation)
|
||||
- [Docker](#docker)
|
||||
- [Docker tags](#docker-tags)
|
||||
- [Environment variables](#environment-variables)
|
||||
- [Swarm command line arguments](#swarm-command-line-arguments)
|
||||
- [Developers Guide](#developers-guide)
|
||||
- [Go Environment](#go-environment)
|
||||
- [Vendored Dependencies](#vendored-dependencies)
|
||||
- [Testing](#testing)
|
||||
- [Profiling Swarm](#profiling-swarm)
|
||||
- [Metrics and Instrumentation in Swarm](#metrics-and-instrumentation-in-swarm)
|
||||
- [Visualizing metrics](#visualizing-metrics)
|
||||
- [Public Gateways](#public-gateways)
|
||||
- [Swarm Dapps](#swarm-dapps)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
## Building the source
|
||||
|
||||
@ -29,17 +36,20 @@ Building Swarm requires Go (version 1.11 or later).
|
||||
|
||||
To simply compile the `swarm` binary without a `GOPATH`:
|
||||
|
||||
$ git clone https://github.com/ethersphere/swarm
|
||||
$ cd swarm
|
||||
$ make swarm
|
||||
```bash
|
||||
$ git clone https://github.com/ethersphere/swarm
|
||||
$ cd swarm
|
||||
$ make swarm
|
||||
```
|
||||
|
||||
You will find the binary under `./build/bin/swarm`.
|
||||
|
||||
To build a vendored `swarm` using `go get` you must have `GOPATH` set. Then run:
|
||||
|
||||
go get -d github.com/ethersphere/swarm
|
||||
|
||||
go install github.com/ethersphere/swarm/cmd/swarm
|
||||
```bash
|
||||
$ go get -d github.com/ethersphere/swarm
|
||||
$ go install github.com/ethersphere/swarm/cmd/swarm
|
||||
```
|
||||
|
||||
## Running Swarm
|
||||
|
||||
@ -47,26 +57,32 @@ Going through all the possible command line flags is out of scope here, but we'v
|
||||
|
||||
To run Swarm you need an Ethereum account. Download and install [Geth](https://geth.ethereum.org) if you don't have it on your system. You can create a new Ethereum account by running the following command:
|
||||
|
||||
geth account new
|
||||
```bash
|
||||
$ geth account new
|
||||
```
|
||||
|
||||
You will be prompted for a password:
|
||||
|
||||
Your new account is locked with a password. Please give a password. Do not forget this password.
|
||||
Passphrase:
|
||||
Repeat passphrase:
|
||||
```
|
||||
Your new account is locked with a password. Please give a password. Do not forget this password.
|
||||
Passphrase:
|
||||
Repeat passphrase:
|
||||
```
|
||||
|
||||
Once you have specified the password, the output will be the Ethereum address representing that account. For example:
|
||||
|
||||
Address: {2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1}
|
||||
```
|
||||
Address: {2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1}
|
||||
```
|
||||
|
||||
Using this account, connect to Swarm with
|
||||
|
||||
swarm --bzzaccount <your-account-here>
|
||||
|
||||
# in our example
|
||||
|
||||
swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1
|
||||
```bash
|
||||
$ swarm --bzzaccount <your-account-here>
|
||||
|
||||
# in our example
|
||||
$ swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1
|
||||
```
|
||||
|
||||
### Verifying that your local Swarm node is running
|
||||
|
||||
@ -78,21 +94,94 @@ Confirm that it is up and running by pointing your browser to http://localhost:8
|
||||
|
||||
The Ethereum Name Service is the Ethereum equivalent of DNS in the classic web. In order to use ENS to resolve names to Swarm content hashes (e.g. `bzz://theswarm.eth`), `swarm` has to connect to a `geth` instance, which is synced with the Ethereum mainnet. This is done using the `--ens-api` flag.
|
||||
|
||||
swarm --bzzaccount <your-account-here> \
|
||||
--ens-api '$HOME/.ethereum/geth.ipc'
|
||||
```bash
|
||||
$ swarm --bzzaccount <your-account-here> \
|
||||
--ens-api '$HOME/.ethereum/geth.ipc'
|
||||
|
||||
# in our example
|
||||
|
||||
swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1 \
|
||||
--ens-api '$HOME/.ethereum/geth.ipc'
|
||||
# in our example
|
||||
$ swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1 \
|
||||
--ens-api '$HOME/.ethereum/geth.ipc'
|
||||
```
|
||||
|
||||
For more information on usage, features or command line flags, please consult the Documentation.
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
Swarm documentation can be found at [https://swarm-guide.readthedocs.io](https://swarm-guide.readthedocs.io).
|
||||
|
||||
## Docker
|
||||
|
||||
Swarm container images are available at Docker Hub: [ethersphere/swarm](https://hub.docker.com/r/ethersphere/swarm)
|
||||
|
||||
### Docker tags
|
||||
|
||||
* `latest` - latest stable release
|
||||
* `edge` - latest build from `master`
|
||||
* `v0.x.y` - specific stable release
|
||||
|
||||
### Environment variables
|
||||
|
||||
* `PASSWORD` - *required* - Used to setup a sample Ethereum account in the data directory. If a data directory is mounted with a volume, the first Ethereum account from it is loaded, and Swarm will try to decrypt it non-interactively with `PASSWORD`
|
||||
* `DATADIR` - *optional* - Defaults to `/root/.ethereum`
|
||||
|
||||
### Swarm command line arguments
|
||||
|
||||
All Swarm command line arguments are supported and can be sent as part of the CMD field to the Docker container.
|
||||
|
||||
**Examples:**
|
||||
|
||||
Running a Swarm container from the command line
|
||||
|
||||
```bash
|
||||
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
||||
--debug \
|
||||
--verbosity 4
|
||||
```
|
||||
|
||||
Running a Swarm container with custom ENS endpoint
|
||||
|
||||
```bash
|
||||
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
||||
--ens-api http://1.2.3.4:8545 \
|
||||
--debug \
|
||||
--verbosity 4
|
||||
```
|
||||
|
||||
Running a Swarm container with metrics enabled
|
||||
|
||||
```bash
|
||||
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
||||
--debug \
|
||||
--metrics \
|
||||
--metrics.influxdb.export \
|
||||
--metrics.influxdb.endpoint "http://localhost:8086" \
|
||||
--metrics.influxdb.username "user" \
|
||||
--metrics.influxdb.password "pass" \
|
||||
--metrics.influxdb.database "metrics" \
|
||||
--metrics.influxdb.host.tag "localhost" \
|
||||
--verbosity 4
|
||||
```
|
||||
|
||||
Running a Swarm container with tracing and pprof server enabled
|
||||
|
||||
```bash
|
||||
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
||||
--debug \
|
||||
--tracing \
|
||||
--tracing.endpoint 127.0.0.1:6831 \
|
||||
--tracing.svc myswarm \
|
||||
--pprof \
|
||||
--pprofaddr 0.0.0.0 \
|
||||
--pprofport 6060
|
||||
```
|
||||
|
||||
Running a Swarm container with custom data directory mounted from a volume
|
||||
|
||||
```bash
|
||||
$ docker run -e DATADIR=/data -e PASSWORD=password123 -v /tmp/hostdata:/data -t ethersphere/swarm \
|
||||
--debug \
|
||||
--verbosity 4
|
||||
```
|
||||
|
||||
## Developers Guide
|
||||
|
||||
@ -104,8 +193,8 @@ You must have your working copy under `$GOPATH/src/github.com/ethersphere/swarm`
|
||||
|
||||
Most likely you will be working from your fork of `swarm`, let's say from `github.com/nirname/swarm`. Clone or move your fork into the right place:
|
||||
|
||||
```
|
||||
git clone git@github.com:nirname/swarm.git $GOPATH/src/github.com/ethersphere/swarm
|
||||
```bash
|
||||
$ git clone git@github.com:nirname/swarm.git $GOPATH/src/github.com/ethersphere/swarm
|
||||
```
|
||||
|
||||
|
||||
@ -124,24 +213,24 @@ This section explains how to run unit, integration, and end-to-end tests in your
|
||||
|
||||
Testing one library:
|
||||
|
||||
```
|
||||
go test -v -cpu 4 ./api
|
||||
```bash
|
||||
$ go test -v -cpu 4 ./api
|
||||
```
|
||||
|
||||
Note: Using options -cpu (number of cores allowed) and -v (logging even if no error) is recommended.
|
||||
|
||||
Testing only some methods:
|
||||
|
||||
```
|
||||
go test -v -cpu 4 ./api -run TestMethod
|
||||
```bash
|
||||
$ go test -v -cpu 4 ./api -run TestMethod
|
||||
```
|
||||
|
||||
Note: here all tests with prefix TestMethod will be run, so if you got TestMethod, TestMethod1, then both!
|
||||
|
||||
Running benchmarks:
|
||||
|
||||
```
|
||||
go test -v -cpu 4 -bench . -run BenchmarkJoin
|
||||
```bash
|
||||
$ go test -v -cpu 4 -bench . -run BenchmarkJoin
|
||||
```
|
||||
|
||||
|
||||
@ -164,11 +253,11 @@ Swarm metrics system is based on the `go-metrics` library.
|
||||
|
||||
The most common types of measurements we use in Swarm are `counters` and `resetting timers`. Consult the `go-metrics` documentation for full reference of available types.
|
||||
|
||||
```
|
||||
# incrementing a counter
|
||||
```go
|
||||
// incrementing a counter
|
||||
metrics.GetOrRegisterCounter("network.stream.received_chunks", nil).Inc(1)
|
||||
|
||||
# measuring latency with a resetting timer
|
||||
// measuring latency with a resetting timer
|
||||
start := time.Now()
|
||||
t := metrics.GetOrRegisterResettingTimer("http.request.GET.time"), nil)
|
||||
...
|
||||
@ -179,8 +268,8 @@ t := UpdateSince(start)
|
||||
|
||||
Swarm supports an InfluxDB exporter. Consult the help section to learn about the command line arguments used to configure it:
|
||||
|
||||
```
|
||||
swarm --help | grep metrics
|
||||
```bash
|
||||
$ swarm --help | grep metrics
|
||||
```
|
||||
|
||||
We use Grafana and InfluxDB to visualise metrics reported by Swarm. We keep our Grafana dashboards under version control at https://github.com/ethersphere/grafana-dashboards. You could use them or design your own.
|
||||
@ -190,12 +279,14 @@ We have built a tool to help with automatic start of Grafana and InfluxDB and pr
|
||||
Once you have `stateth` installed, and you have Docker running locally, you have to:
|
||||
|
||||
1. Run `stateth` and keep it running in the background
|
||||
```
|
||||
stateth --rm --grafana-dashboards-folder $GOPATH/src/github.com/ethersphere/grafana-dashboards --influxdb-database metrics
|
||||
|
||||
```bash
|
||||
$ stateth --rm --grafana-dashboards-folder $GOPATH/src/github.com/ethersphere/grafana-dashboards --influxdb-database metrics
|
||||
```
|
||||
|
||||
2. Run `swarm` with at least the following params:
|
||||
```
|
||||
|
||||
```bash
|
||||
--metrics \
|
||||
--metrics.influxdb.export \
|
||||
--metrics.influxdb.endpoint "http://localhost:8086" \
|
||||
@ -245,10 +336,10 @@ Please make sure your contributions adhere to our coding guidelines:
|
||||
|
||||
## License
|
||||
|
||||
The go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the
|
||||
The swarm library (i.e. all code outside of the `cmd` directory) is licensed under the
|
||||
[GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html), also
|
||||
included in our repository in the `COPYING.LESSER` file.
|
||||
|
||||
The go-ethereum binaries (i.e. all code inside of the `cmd` directory) is licensed under the
|
||||
The swarm binaries (i.e. all code inside of the `cmd` directory) is licensed under the
|
||||
[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), also included
|
||||
in our repository in the `COPYING` file.
|
||||
|
13
build/ci.go
@ -94,7 +94,7 @@ var (
|
||||
// Note: yakkety is unsupported because it was officially deprecated on lanchpad.
|
||||
// Note: zesty is unsupported because it was officially deprecated on lanchpad.
|
||||
// Note: artful is unsupported because it was officially deprecated on lanchpad.
|
||||
debDistros = []string{"trusty", "xenial", "bionic", "cosmic"}
|
||||
debDistros = []string{"trusty", "xenial", "bionic", "cosmic", "disco"}
|
||||
)
|
||||
|
||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||
@ -431,17 +431,16 @@ func doDebianSource(cmdline []string) {
|
||||
build.MustRun(debuild)
|
||||
|
||||
var (
|
||||
basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString())
|
||||
source = filepath.Join(*workdir, basename+".tar.xz")
|
||||
dsc = filepath.Join(*workdir, basename+".dsc")
|
||||
changes = filepath.Join(*workdir, basename+"_source.changes")
|
||||
buildinfo = filepath.Join(*workdir, basename+"_source.buildinfo")
|
||||
basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString())
|
||||
source = filepath.Join(*workdir, basename+".tar.xz")
|
||||
dsc = filepath.Join(*workdir, basename+".dsc")
|
||||
changes = filepath.Join(*workdir, basename+"_source.changes")
|
||||
)
|
||||
if *signer != "" {
|
||||
build.MustRunCommand("debsign", changes)
|
||||
}
|
||||
if *upload != "" {
|
||||
ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes, buildinfo})
|
||||
ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,10 +178,22 @@ func trackChunks(testData []byte, submitMetrics bool) error {
|
||||
// getChunksBitVectorFromHost returns a bit vector of presence for a given slice of chunks from a given host
|
||||
func getChunksBitVectorFromHost(client *rpc.Client, addrs []storage.Address) (string, error) {
|
||||
var hostChunks string
|
||||
const trackChunksPageSize = 7500
|
||||
|
||||
err := client.Call(&hostChunks, "bzz_has", addrs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
for len(addrs) > 0 {
|
||||
var pageChunks string
|
||||
// get current page size, so that we avoid a slice out of bounds on the last page
|
||||
pagesize := trackChunksPageSize
|
||||
if len(addrs) < trackChunksPageSize {
|
||||
pagesize = len(addrs)
|
||||
}
|
||||
|
||||
err := client.Call(&pageChunks, "bzz_has", addrs[:pagesize])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hostChunks += pageChunks
|
||||
addrs = addrs[pagesize:]
|
||||
}
|
||||
|
||||
return hostChunks, nil
|
||||
|
@ -58,7 +58,7 @@ func create(ctx *cli.Context) error {
|
||||
func createSnapshot(filename string, nodes int, services []string) (err error) {
|
||||
log.Debug("create snapshot", "filename", filename, "nodes", nodes, "services", services)
|
||||
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
sim := simulation.NewInProc(map[string]simulation.ServiceFunc{
|
||||
"bzz": func(ctx *adapters.ServiceContext, bucket *sync.Map) (node.Service, func(), error) {
|
||||
addr := network.NewAddr(ctx.Config.Node())
|
||||
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||
|
@ -4,4 +4,4 @@ set -o errexit
|
||||
set -o pipefail
|
||||
set -o nounset
|
||||
|
||||
/swarm-smoke $@ 2>&1 || true
|
||||
swarm-smoke $@ 2>&1 || true
|
||||
|
36
docs/Release-Process.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Release process for Swarm
|
||||
|
||||
This document describes the steps required to perform a new release.
|
||||
|
||||
## Pre release
|
||||
|
||||
1. Make sure that the most recent builds are green and that smoke tests are passing on the cluster.
|
||||
2. Check if protocols should be bumped. (e.g [PR#1465](https://github.com/ethersphere/swarm/pull/1465))
|
||||
3. Check if the [CHANGELOG.md](../CHANGELOG.md) is reflecting all the changes.
|
||||
|
||||
## Release
|
||||
|
||||
1. Create a PR to update `version.go` and `CHANGELOG.md`. [See example PR](https://github.com/ethersphere/swarm/pull/1469).
|
||||
2. Merge the PR after all tests passed.
|
||||
3. Tag the merged commit that went into `master`.
|
||||
```sh
|
||||
git checkout master
|
||||
git pull
|
||||
git tag v0.4.{x}
|
||||
git push origin v0.4.{x}
|
||||
```
|
||||
4. CI Builds (Travis/Appveyor/DockerHub) will trigger. Wait for them to finish.
|
||||
5. Verify that the following places have the new release:
|
||||
1. [ ] [Website download page](https://ethswarm.org/downloads/)
|
||||
2. [ ] [Docker Hub](https://hub.docker.com/r/ethersphere/swarm/tags)
|
||||
3. [ ] [Ubuntu PPA](https://launchpad.net/~ethereum/+archive/ubuntu/ethereum/+packages?field.name_filter=ethereum-swarm&field.status_filter=published&field.series_filter=)
|
||||
6. Create a PR to update `version.go` and `CHANGELOG.md`, this time setting it to `unstable` and increasing the version number: [See example PR](https://github.com/ethersphere/swarm/pull/1470).
|
||||
7. Merge the PR after all tests passed.
|
||||
8. Close the [milestone](https://github.com/ethersphere/swarm/milestones).
|
||||
|
||||
## Post release
|
||||
|
||||
1. Update bootnodes and the nodes serving swarm-gateways.net
|
||||
2. Announce the release on social media:
|
||||
1. Create post on reddit: https://www.reddit.com/r/ethswarm/
|
||||
2. Share post on gitter: [ethersphere/orange-lounge](https://gitter.im/ethersphere/orange-lounge) and [ethereum/swarm](https://gitter.im/ethereum/swarm)
|
160
docs/Stream-Protocol-Spec.md
Normal file
@ -0,0 +1,160 @@
|
||||
|
||||
stream! protocol
|
||||
======
|
||||
|
||||
| Subject | Description |
|
||||
|---|---|
|
||||
| Authors | @zelig, @acud, @nonsense |
|
||||
| Status | Draft |
|
||||
| Created | 2019-06-11 |
|
||||
|
||||
|
||||
### definition of stream
|
||||
a protocol that facilitates data transmission between two swarm nodes, specifically targeting sequential data in the form of a sequence of chunks as defined by swarm. the protocol should cater for the following requirements:
|
||||
- client should be able to request arbitrary ranges from the server
|
||||
- client can be assumed to have some of the data already and therefore can opt in to selectivally request chunks based on their hashes
|
||||
|
||||
As mentioned, the client is typically expected to have some of the data in the stream. to mitigate duplicate data transmission the stream protocol provides a configurable message roundtrip before batch delivery which allows the downstream peer to selectively request the chunks which it does not store at the time of the request.
|
||||
When delivery batches are pre-negotiated (i.e. when the client selectively tells the server which chunks it would like to receive), we can conclude that the delivery batches are optimsed for _urgency_ rather than for maximising batch utilisation (since the server sends a certain batch that potentially gets reduced into a smaller one by the client before actually being transmitted).
|
||||
|
||||
### the protocol defines the notions of:
|
||||
- **stream** - data source which is composed of a sequence of hashes, referenced by monotonically increasing integers, with unguaranteed contiguity with respect to one particular stream.
|
||||
- **client** _(downstream peer)_ - the peer which is requesting data and does not posses it (client)
|
||||
- **server** _(upstream peer)_ - the peer that has the data and sends it to the downstream peer (server)
|
||||
- **range** - based on the notion of integer indexes we can define a range that designates an interval on the stream.
|
||||
- **batch** - a set of chunks constituting an interval in a range are called a batch, with a length not exceeding a ceiling value negotiated when establishing streams
|
||||
- **batch delivery** - end of batch delivery should be indicated by an explicit message from the server
|
||||
both offered and wanted go together - note this
|
||||
- **roundtrip** - a configurable extra message exchange (negotiated on initial message exchange) meant to mitigate and avoid requesting the same data from different peers. when the roundtrip is not used the stream is assumed to be continuous and the order of delivery should be guaranteed, or the stream should be particularly ordered. A roundtrip consists of:
|
||||
- **offered hashes** - from the server to the client
|
||||
- **wanted hashes** - at the discretion of the client in response to offered hashes
|
||||
|
||||
### responsibilities:
|
||||
- client is able to request a range but doesnt know how many results the interval will return from the server
|
||||
- client does not know if interval is continuous or has gaps
|
||||
- range is defined by client and should be strictly respected and followed by server
|
||||
- all intervals specified in protocol messages are closed (inclusive)
|
||||
- when roundtrip is configured - chunk deliveries can be handled concurrently (therefore their order is not guaranteed), but a server end-of-batch with topmost session index must be sent to signal the end of a batch
|
||||
- when roundtrip is not configured - chunks are expected to be sent in order, one after the other
|
||||
- when a client requests an unbounded range (i.e. FROM=..., TO=nil):
|
||||
- if there's no chunks available - server waits until something becomes available then send it to the client
|
||||
- server's responsibility to give as much as possible, as fast as possible, with a limit of batch size
|
||||
- one range query should result in ONE rountrip + batch delivery
|
||||
- when a client requests a bounded range, server should respond to the client range requests with either offered hashes (if roundtrip is required) or chunks (if not) or an end-of-batch message if there are no more to offer. If none of these responses arrive within a timeout interval, client must drop the upstream peer.
|
||||
- the server should always respond to the client
|
||||
|
||||
|
||||
#### stream termination condition:
|
||||
- timeout, connection died, we get an error and remove the client, server also gets an error from p2p layer and removes all servers/clients and drops the peer
|
||||
|
||||
### considerations:
|
||||
- server must make sure that chunk got to client in order to account in SWAP (synchronous). if the send does not result in an error - the send should be accounted
|
||||
- there is always a max batch size so that clients cannot grieve servers with very large ranges
|
||||
|
||||
### syncing contracts:
|
||||
- stream indexes always > 0
|
||||
- syncing is an implementation of the stream protocol
|
||||
- client is expected to manage all intervals, and therefore:
|
||||
- server is designed to be stateless, except for the case of managing a offered/wanted roundtrip and the knowledge of a boundedness of a stream (e.g. the server knows that syncing streams are always unbounded from the localstore perspective - data can always enter the system, however this is not the case for live video stream for example)
|
||||
- the server does not terminate streams - it is at the discretion of the downstream peer
|
||||
- the server does not initiate any messages unless instructed to
|
||||
- the server does not instruct client on which bins to subscribe to it
|
||||
|
||||
Wire Protocol Specifications
|
||||
=======
|
||||
|
||||
### The wire protocol defines the following messages:
|
||||
|
||||
| Msg Name | From->To | Params | Example |
|
||||
| -------- | -------- | -------- | ------- |
|
||||
| StreamInfoReq | Client->Server | Streams`[]string` | `SYNC\|6, SYNC\|5` |
|
||||
| StreamInfoRes | Server->Client | Streams`[]StreamDescriptor` <br>Stream`string`<br>Cursor`uint64`<br>Bounded`bool` | `SYNC\|6;CUR=1632;bounded, SYNC\|7;CUR=18433;bounded` |
|
||||
| GetRange | Client->Server| Ruid`uint`<br>Stream `string`<br>From`uint`<br>To`*uint`(nullable)<br>Roundtrip`bool` | `Ruid: 21321, Stream: SYNC\|6, From: 1, To: 100`(bounded), Roundtrip: true<br>`Stream: SYNC\|7, From: 109, Roundtrip: true`(unbounded) |
|
||||
| OfferedHashes | Server->Client| Ruid`uint`<br>Hashes `[]byte` | `Ruid: 21321, Hashes: [cbcbbaddda, bcbbbdbbdc, ....]` |
|
||||
| WantedHashes | Client->Server | Ruid`uint`<br>Bitvector`[]byte` | `Ruid: 21321, Bitvector: [0100100100] ` |
|
||||
| ChunkDelivery | Server->Client | Ruid`uint`<br>[]Chunk `[]byte` | `Ruid: 21321, Chunk: [001000101]` |
|
||||
| BatchDone | Server->Client| Ruid `uint`<br>Last `uint` | `Ruid: 21321, Last: 113331` |
|
||||
| StreamState | Client<->Server | Stream`string`<br>Code`uint16`<br>Message`string`| `Stream: SYNC\|6, Code:1, Message:"Stream became bounded"`<br>`Stream: SYNC\|5, Code:2, Message: "No such stream"` |
|
||||
|
||||
Notes:
|
||||
* communicating the last bin index when roundtrip is configured - can be done on top of OfferedHashes message (alongside the hashes), or to reuse the ACK from the no-roundtrip config
|
||||
* two notions of bounded - on the stream level and on the localstore
|
||||
* if TO is not specified - we assume unbounded stream, and we just send whatever, until at most, we fill up an entire batch.
|
||||
|
||||
### Message struct definitions:
|
||||
```go
|
||||
type StreamInfoReq struct {
|
||||
Streams []string
|
||||
}
|
||||
```
|
||||
```go
|
||||
type StreamInfoRes struct {
|
||||
Streams []StreamDescriptor
|
||||
}
|
||||
```
|
||||
```go
|
||||
type StreamDescriptor struct {
|
||||
Name string
|
||||
Cursor uint
|
||||
Bounded bool
|
||||
}
|
||||
```
|
||||
```go
|
||||
type GetRange struct {
|
||||
Ruid uint
|
||||
Stream string
|
||||
From uint
|
||||
To uint `rlp:nil`
|
||||
BatchSize uint
|
||||
Roundtrip bool
|
||||
}
|
||||
```
|
||||
```go
|
||||
type OfferedHashes struct {
|
||||
Ruid uint
|
||||
LastIndex uint
|
||||
Hashes []byte
|
||||
}
|
||||
```
|
||||
```go
|
||||
type WantedHashes struct {
|
||||
Ruid uint
|
||||
BitVector []byte
|
||||
}
|
||||
```
|
||||
```go
|
||||
type ChunkDelivery struct {
|
||||
Ruid uint
|
||||
LastIndex uint
|
||||
Chunks [][]byte
|
||||
}
|
||||
```
|
||||
```go
|
||||
type BatchDone struct {
|
||||
Ruid uint
|
||||
Last uint
|
||||
}
|
||||
```
|
||||
```go
|
||||
type StreamState struct {
|
||||
Stream string
|
||||
Code uint16
|
||||
Message string
|
||||
}
|
||||
```
|
||||
|
||||
Message exchange examples:
|
||||
======
|
||||
|
||||
Initial handshake - client queries server for stream states<br>
|
||||

|
||||
<br>
|
||||
GetRange (bounded) - client requests a bounded range within a stream<br>
|
||||

|
||||
<br>
|
||||
GetRange (unbounded) - client requests an unbounded range (specifies only `From` parameter)<br>
|
||||

|
||||
<br>
|
||||
GetRange (no roundtrip) - client requests an unbounded or bounded range with no roundtrip configured<br>
|
||||

|
||||
|
BIN
docs/diagrams/stream-bounded.dia
Normal file
BIN
docs/diagrams/stream-bounded.png
Normal file
After Width: | Height: | Size: 31 KiB |
105
docs/diagrams/stream-bounded.svg
Normal file
@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
|
||||
<svg width="24cm" height="27cm" viewBox="257 -21 480 532" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="560" y1="55.4873" x2="559.021" y2="498.679"/>
|
||||
<polygon style="fill: #1b1b1b" points="559.005,506.179 554.027,496.168 559.021,498.679 564.027,496.19 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="559.005,506.179 554.027,496.168 559.021,498.679 564.027,496.19 "/>
|
||||
</g>
|
||||
<text font-size="12.8" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="372" y="46.5">
|
||||
<tspan x="372" y="46.5">Client</tspan>
|
||||
</text>
|
||||
<text font-size="12.8" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="545" y="46.5">
|
||||
<tspan x="545" y="46.5">Server</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="402.1" y1="80.1" x2="540.364" y2="80.1"/>
|
||||
<polygon style="fill: #1b1b1b" points="547.864,80.1 537.864,85.1 540.364,80.1 537.864,75.1 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="547.864,80.1 537.864,85.1 540.364,80.1 537.864,75.1 "/>
|
||||
</g>
|
||||
<text font-size="12.8" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="445.018" y="65.1">
|
||||
<tspan x="445.018" y="65.1">GetRange</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="546.782" y1="183.25" x2="417.618" y2="182.181"/>
|
||||
<polygon style="fill: #1b1b1b" points="410.118,182.119 420.159,177.201 417.618,182.181 420.076,187.201 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="410.118,182.119 420.159,177.201 417.618,182.181 420.076,187.201 "/>
|
||||
</g>
|
||||
<text font-size="12.8" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="429.318" y="171.1">
|
||||
<tspan x="429.318" y="171.1">OfferedHashes</tspan>
|
||||
</text>
|
||||
<text font-size="12.8" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="257.05" y="82.1">
|
||||
<tspan x="257.05" y="82.1">1. Client requests</tspan>
|
||||
<tspan x="257.05" y="98.1">an arbitrary</tspan>
|
||||
<tspan x="257.05" y="114.1">range of a stream</tspan>
|
||||
<tspan x="257.05" y="130.1">e.g. From: 1, To: 13</tspan>
|
||||
</text>
|
||||
<text font-size="12.8" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="585.287" y="157.1">
|
||||
<tspan x="585.287" y="157.1">2. Server replies with</tspan>
|
||||
<tspan x="585.287" y="173.1">possible offered hashes</tspan>
|
||||
<tspan x="585.287" y="189.1">in the requested range</tspan>
|
||||
</text>
|
||||
<text font-size="12.8" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="413.05" y="-8.51248">
|
||||
<tspan x="413.05" y="-8.51248">Bounded GetRange</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="407.1" y1="272.25" x2="535.364" y2="272.25"/>
|
||||
<polygon style="fill: #1b1b1b" points="542.864,272.25 532.864,277.25 535.364,272.25 532.864,267.25 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="542.864,272.25 532.864,277.25 535.364,272.25 532.864,267.25 "/>
|
||||
</g>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="428.893" y="259.25">
|
||||
<tspan x="428.893" y="259.25">WantedHashes</tspan>
|
||||
</text>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="273" y="259.25">
|
||||
<tspan x="273" y="259.25">3. Client replies</tspan>
|
||||
<tspan x="273" y="275.25">with a (sub)set</tspan>
|
||||
<tspan x="273" y="291.25">of wanted chunk</tspan>
|
||||
<tspan x="273" y="307.25">hashes</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="546.782" y1="351.185" x2="417.618" y2="350.115"/>
|
||||
<polygon style="fill: #1b1b1b" points="410.118,350.053 420.159,345.136 417.618,350.115 420.076,355.136 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="410.118,350.053 420.159,345.136 417.618,350.115 420.076,355.136 "/>
|
||||
</g>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="423.518" y="336">
|
||||
<tspan x="423.518" y="336">Chunk Deliveries</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="546.782" y1="370.185" x2="417.618" y2="369.115"/>
|
||||
<polygon style="fill: #1b1b1b" points="410.118,369.053 420.159,364.136 417.618,369.115 420.076,374.136 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="410.118,369.053 420.159,364.136 417.618,369.115 420.076,374.136 "/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="546.782" y1="388.185" x2="417.618" y2="387.115"/>
|
||||
<polygon style="fill: #1b1b1b" points="410.118,387.053 420.159,382.136 417.618,387.115 420.076,392.136 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="410.118,387.053 420.159,382.136 417.618,387.115 420.076,392.136 "/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="546.782" y1="407.185" x2="417.618" y2="406.115"/>
|
||||
<polygon style="fill: #1b1b1b" points="410.118,406.053 420.159,401.136 417.618,406.115 420.076,411.136 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="410.118,406.053 420.159,401.136 417.618,406.115 420.076,411.136 "/>
|
||||
</g>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="585.287" y="373">
|
||||
<tspan x="585.287" y="373">4. Server delivers</tspan>
|
||||
<tspan x="585.287" y="389">requested chunks</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="546.782" y1="452.185" x2="417.618" y2="451.115"/>
|
||||
<polygon style="fill: #1b1b1b" points="410.118,451.053 420.159,446.136 417.618,451.115 420.076,456.136 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="410.118,451.053 420.159,446.136 417.618,451.115 420.076,456.136 "/>
|
||||
</g>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="448" y="440">
|
||||
<tspan x="448" y="440">BatchDone</tspan>
|
||||
</text>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="585.287" y="446">
|
||||
<tspan x="585.287" y="446">5. Server reports to</tspan>
|
||||
<tspan x="585.287" y="462">client that batch has</tspan>
|
||||
<tspan x="585.287" y="478">completed with the </tspan>
|
||||
<tspan x="585.287" y="494">last delivered bin index</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="388.309" y1="55.4873" x2="387.331" y2="498.679"/>
|
||||
<polygon style="fill: #1b1b1b" points="387.314,506.179 382.336,496.168 387.331,498.679 392.336,496.19 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="387.314,506.179 382.336,496.168 387.331,498.679 392.336,496.19 "/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.7 KiB |
BIN
docs/diagrams/stream-handshake.dia
Normal file
BIN
docs/diagrams/stream-handshake.png
Normal file
After Width: | Height: | Size: 12 KiB |
50
docs/diagrams/stream-handshake.svg
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
|
||||
<svg width="23cm" height="13cm" viewBox="269 -21 460 256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="560" y1="56.0723" x2="560.992" y2="223.573"/>
|
||||
<polygon style="fill: #1b1b1b" points="561.037,231.073 555.978,221.102 560.992,223.573 565.977,221.043 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="561.037,231.073 555.978,221.102 560.992,223.573 565.977,221.043 "/>
|
||||
</g>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="372" y="46.5">
|
||||
<tspan x="372" y="46.5">Client</tspan>
|
||||
</text>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="545" y="46.5">
|
||||
<tspan x="545" y="46.5">Server</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="402.1" y1="80.1" x2="540.364" y2="80.1"/>
|
||||
<polygon style="fill: #1b1b1b" points="547.864,80.1 537.864,85.1 540.364,80.1 537.864,75.1 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="547.864,80.1 537.864,85.1 540.364,80.1 537.864,75.1 "/>
|
||||
</g>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="455.1" y="65.1">
|
||||
<tspan x="455.1" y="65.1">Stream</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="553.1" y1="109.1" x2="415.808" y2="177.746"/>
|
||||
<polygon style="fill: #1b1b1b" points="409.1,181.1 415.808,172.156 415.808,177.746 420.28,181.1 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="409.1,181.1 415.808,172.156 415.808,177.746 420.28,181.1 "/>
|
||||
</g>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="426.1" y="131.1">
|
||||
<tspan x="426.1" y="131.1">StreamAck</tspan>
|
||||
</text>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="269.05" y="83.1">
|
||||
<tspan x="269.05" y="83.1">1. Client requests</tspan>
|
||||
<tspan x="269.05" y="99.1">info about</tspan>
|
||||
<tspan x="269.05" y="115.1">streams</tspan>
|
||||
</text>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="592.05" y="96.1">
|
||||
<tspan x="592.05" y="96.1">2. Server replies with</tspan>
|
||||
<tspan x="592.05" y="112.1">stream info: names,</tspan>
|
||||
<tspan x="592.05" y="128.1">session indexes and</tspan>
|
||||
<tspan x="592.05" y="144.1">boundedness</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" x1="387.808" y1="56.0723" x2="388.801" y2="223.573"/>
|
||||
<polygon style="fill: #1b1b1b" points="388.845,231.073 383.786,221.102 388.801,223.573 393.786,221.043 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #1b1b1b" points="388.845,231.073 383.786,221.102 388.801,223.573 393.786,221.043 "/>
|
||||
</g>
|
||||
<text font-size="12.7998" style="fill: #1b1b1b;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="413.05" y="-8.51248">
|
||||
<tspan x="413.05" y="-8.51248">Establishing a stream</tspan>
|
||||
</text>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
BIN
docs/diagrams/stream-no-roundtrip.dia
Normal file
BIN
docs/diagrams/stream-no-roundtrip.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
docs/diagrams/stream-unbounded.dia
Normal file
BIN
docs/diagrams/stream-unbounded.png
Normal file
After Width: | Height: | Size: 33 KiB |
1
docs/swarm-guide/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build
|
9
docs/swarm-guide/Dockerfile
Normal file
@ -0,0 +1,9 @@
|
||||
FROM python:3.7.1
|
||||
|
||||
RUN pip install \
|
||||
Sphinx==1.8.2 \
|
||||
sphinx-rtd-theme==0.4.2 \
|
||||
sphinx-tabs==1.1.10
|
||||
|
||||
RUN mkdir -p /src
|
||||
WORKDIR /src
|
198
docs/swarm-guide/Makefile
Normal file
@ -0,0 +1,198 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = build
|
||||
SOURCE = contents
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SOURCE)
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SOURCE)
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " applehelp to make an Apple Help Book"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
html-docker:
|
||||
@which docker &> /dev/null || (echo "please, install Docker to use this target" >&2 && exit 1)
|
||||
@docker build --tag swarm-sphinx .
|
||||
@docker run --rm --volume $$PWD:/src swarm-sphinx make html
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/homesteadguide.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/homesteadguide.qhc"
|
||||
|
||||
applehelp:
|
||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||
@echo
|
||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
||||
"~/Library/Documentation/Help or install it in your application" \
|
||||
"bundle."
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/homesteadguide"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/homesteadguide"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
coverage:
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||
@echo "Testing of coverage in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/coverage/python.txt."
|
||||
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
25
docs/swarm-guide/README.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Swarm Guide
|
||||
|
||||
Swarm's documentation in sphinx, hosted on read the docs:
|
||||
http://swarm-guide.readthedocs.io
|
||||
|
||||
## Building the source
|
||||
|
||||
After building the source you will find `index.html` in `./build/html/` folder.
|
||||
|
||||
### Requirements
|
||||
|
||||
- GNU Make
|
||||
- Docker or Python (pip)
|
||||
|
||||
### Using Docker
|
||||
|
||||
After you have `docker` available just call `make html-docker`.
|
||||
|
||||
### Native with Python
|
||||
|
||||
Execute
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
make html
|
||||
```
|
32
docs/swarm-guide/contents/_static/theme_overrides.css
Normal file
@ -0,0 +1,32 @@
|
||||
/* override table width restrictions */
|
||||
@media screen and (min-width: 767px) {
|
||||
|
||||
.wy-table-responsive table td {
|
||||
/* !important prevents the common CSS stylesheets from overriding
|
||||
this as on RTD they are loaded after this stylesheet */
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
.wy-table-responsive {
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* override table width restrictions */
|
||||
/* .wy-table-responsive table td, .wy-table-responsive table th {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.wy-table-responsive {
|
||||
margin-bottom: 24px;
|
||||
max-width: 100%;
|
||||
overflow: visible;
|
||||
} */
|
||||
|
||||
.wy-grid-for-nav section {
|
||||
background-color: #fcfcfc !important;
|
||||
}
|
||||
|
||||
.wy-nav-content {
|
||||
max-width: auto !important;
|
||||
}
|
302
docs/swarm-guide/contents/apireference.rst
Normal file
@ -0,0 +1,302 @@
|
||||
.. _API Reference:
|
||||
|
||||
*************************
|
||||
API reference
|
||||
*************************
|
||||
|
||||
|
||||
|
||||
HTTP
|
||||
=========================
|
||||
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| Name | Method | Descriptors | |
|
||||
+===============+========+===============+=============================================================================================================================================================================================================================================================================================================================================================+
|
||||
| bzz | GET | Purpose | retrieve document at domain/some/path allowing domain to resolve via the :ref:`Ethereum Name Service` |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator | bzz:/<domain_part>/<resource_path> |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator Parts | domain part: mandatory - ENS name or a valid Swarm hash. path part: optional - a case insensitive path to match for in the manifest |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | HTTP Codes | 200; 300; 404; 500 |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Responds with | The content stored at the resolved ENS entry (or the matched path) with the appropriate Content-Type as stored in the manifest |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Example | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | POST | Purpose | post an application/x-tar or multipart/form-data (or any other Content-Type for that matter); create an appropriate manifest and retrieve the associated hash. if an existing manifest address is given with the according path to update - a copy of the manifest will be created with the updated entry and the hash of the new manifest will be returned |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator | bzz:/<manifest_hash?>/<resource_path?>/<encrypt?> |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator Parts | manifest hash - optional - an existing manifest address to update a resource included in the manifest. resource path - optional - which resource to update in the manifest. encrypt - optional flag to enable encryption |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | HTTP Codes | 200 |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Responds with | a hash of a newly created manifest |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Example | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | DELETE | Purpose | delete a resource from a manifest by unlinking it from the existing manifest |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator | bzz:/<domain>/<path> |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator Parts | domain part - mandatory - a valid ENS hash or a valid Swarm manifest hash. path part - mandatory - a path to the resource to be removed from the manifest |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | HTTP Codes | 200; 404; 500 |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Responds with | the hash of the new manifest which does not have component ``path`` |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Example | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| bzz-immutable | GET | Purpose | The same as the generic scheme but there is no ENS domain resolution. the domain part of the path needs to be a valid hash. This is also a read-only scheme but explicit in its integrity protection. A particular bzz-immutable url will always necessarily address the exact same fixed immutable content. |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator | bzz-immutable:/<hash> |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator Parts | hash part - a valid Swarm hash that points to a manifest |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | HTTP Codes | 200; 404; 500 |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Responds with | the resolved content at the specified address with a valid content-type as stored in the manifest |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Example | use bzz-hash to resolve the ens name into a hash then use it with immutable |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| bzz-raw | GET | Purpose | When responding to GET requests with the bzz-raw scheme swarm does not assume a manifest but just serves the asset addressed by the url directly. The ``content_type`` query parameter can be supplied to specify the mime type you are requesting otherwise content is served as an octet stream per default. |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator | bzz-raw:/<content_hash>?content_type=<mime> |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator Parts | content hash - mandatory - a valid Swarm content hash. content type - optional - the mime type to serve the content as. defaults to application/octet-stream |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | HTTP Codes | 200; 404; 500 |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Responds with | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Example | a pdf document (not the manifest wrapping it) resides at hash 6a182226... then the following url will properly serve it: GET http://localhost:8500/bzz-raw:/6a18222637cafb4ce692fa11df886a03e6d5e63432c53cbf7846970aa3e6fdf5?content_type=application/pdf |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | POST | Purpose | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator Parts | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | HTTP Codes | 200; 404; 500 |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Responds with | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Example | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| bzz-list | GET | Purpose | Returns a list of all files contained in <manifest> under <path> grouped into common prefixes using ``/`` as a delimiter. If path is ``/`` - all files in manifest are returned. The response is a JSON-encoded object with ``common_prefixes`` string field and ``entries`` list field. |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator | bzz-list:/<domain>/<path> |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator Parts | domain part - mandatory - a valid ENS entry that points to a valid manifest hash or a valid manifest hash. path part - optional - path to look for inside the manifest |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | HTTP Codes | 200; 404; 500 |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Responds with | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Example | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| bzz-hash | GET | Purpose | responds with the hash value of the raw content - the same content returned by requests with bzz-raw scheme. Hash of the manifest is also the hash stored in ENS so bzz-hash can be used for ENS domain resolution |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator | bzz-hash:/<domain> |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator Parts | domain part - mandatory. a valid ENS name |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | HTTP Codes | 200; 404; 500 |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Responds with | text/plain |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Example | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| bzz-feed | GET | Purpose | Retrieve a Feed update |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator | bzz-feed:/<hash>?user=<user>&topic=<topic>&name=<name>&time=<t> |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator Parts | hash - optional - manifest hash of the Feed, otherwise user param required. user - optional - Ethereum address of the user who publishes the Feed. topic - optional - Feed topic, encoded as a hex string, default: 0. name - optional - subtopic that is combined with the topic, default: "". |
|
||||
| | | | time - optional - The last update before that time (unix time) will be looked up, default: most recent update. meta - optional - Just return the Feed metadata, default 0 (false). |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | HTTP Codes | 200; 400; 404; 500 |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Responds with | The content stored in the requested Feed update |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Example | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | GET | Purpose | Get Feed metadata, used to help publishing updates |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator | bzz-feed:/<hash>?user=<user>&topic=<topic>&name=<name>&meta=1 |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator Parts | hash - optional - manifest hash of the Feed, otherwise user param required. user - optional - Ethereum address of the account that owns the Feed. topic - optional - Feed topic, encoded as a hex string, default: 0. name - optional - subtopic that is combined with the topic, default: "". |
|
||||
| | | | meta - required - Return the Feed metadata instead of the Feed content. |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | HTTP Codes | 200; 400; 500 |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Responds with | application/json |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Example | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | POST | Purpose | Post an update to a Feed |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator | bzz-feed:/<hash>?user=<user>&topic=<topic>&level=<level>&time=<time>&protocolVersion=<ver>&signature=<sig> |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Locator Parts | hash - optional - manifest hash of the Feed, otherwise user param required. user - optional - Ethereum address of the account that owns the Feed. topic - optional - Feed topic, encoded as a hex string, default: 0. level - optional - suggested frequency level (retreived above). time - optional - suggested timestamp (retrieved above). |
|
||||
| | | | protocolVersion - optional - Feed protocol version, default: current version. signature - required - Feed signature hex encoded |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | HTTP Codes | 200; 400; 500 |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Responds with | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| | | Example | |
|
||||
+---------------+--------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
JavaScript
|
||||
========================
|
||||
Swarm currently supports a Javascript API through a few packages:
|
||||
|
||||
erebos
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
`erebos <https://erebos.js.org>`_ is available through `NPM <https://www.npmjs.com/package/@erebos/swarm>`_ by issuing
|
||||
the following command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
npm install @erebos/swarm-browser # browser only
|
||||
npm install @erebos/swarm-node # node only
|
||||
npm install @erebos/swarm # universal
|
||||
|
||||
|
||||
.. note:: Full documentation is available on the `documentation website <https://erebos.js.org>`_.
|
||||
|
||||
swarm-js
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
`swarm-js <https://github.com/MaiaVictor/swarm-js>`_ is available through `NPM <https://www.npmjs.com/package/swarm-js>`_ by issuing
|
||||
the following command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
npm install swarm-js
|
||||
|
||||
|
||||
.. note:: Full documentation is available on the `GitHub <https://github.com/MaiaVictor/swarm-js>`_ page.
|
||||
|
||||
swarmgw
|
||||
^^^^^^^^^^
|
||||
|
||||
`swarmgw <https://github.com/axic/swarmgw>`_ is available through `NPM <https://www.npmjs.com/package/swarmgw>`_ by issuing
|
||||
the following command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
npm install swarmgw
|
||||
|
||||
When installed globally, it can also be used directly from the CLI:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
npm install -g swarmgw
|
||||
|
||||
.. note:: Full documentation is available on the `GitHub <https://github.com/axic/swarmgw>`_ page.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
RPC
|
||||
========================
|
||||
|
||||
Swarm exposes an IPC API under the ``bzz`` namespace.
|
||||
|
||||
|
||||
FUSE
|
||||
^^^^^^
|
||||
|
||||
``swarmfs.mount(HASH|domain, mountpoint))``
|
||||
mounts Swarm contents represented by a Swarm hash or a ens domain name to the specified local directory. The local directory has to be writable and should be empty.
|
||||
Once this command is succesfull, you should see the contents in the local directory. The HASH is mounted in a rw mode, which means any change insie the directory will be automatically reflected in Swarm. Ex: if you copy a file from somewhere else in to mountpoint, it is equvivalent of using a ``swarm up <file>`` command.
|
||||
|
||||
``swarmfs.unmount(mountpoint)``
|
||||
This command unmounts the HASH|domain mounted in the specified mountpoint. If the device is busy, unmounting fails. In that case make sure you exit the process that is using the directory and try unmounting again.
|
||||
|
||||
``swarmfs.listmounts()``
|
||||
For every active mount, this command display three things. The mountpoint, start HASH supplied and the latest HASH. Since the HASH is mounted in rw mode, when ever there is a change to the file system (adding file, removing file etc), a new HASH is computed. This hash is called the latest HASH.
|
||||
|
||||
PSS
|
||||
^^^^^
|
||||
|
||||
``pss`` methods are by default exposed via IPC. If websockets are activated on the node, they will also be available there *if* the ``pss`` module is explicitly specified.
|
||||
|
||||
All parameters are hex-encoded bytes or strings unless otherwise noted.
|
||||
|
||||
``pss.getPublicKey()``
|
||||
Retrieves the public key of the node, in hex format
|
||||
|
||||
``pss.baseAddr()``
|
||||
Retrieves the Swarm overlay address of the node, in hex format
|
||||
|
||||
``pss.stringToTopic(name)``
|
||||
Creates a deterministic 4 byte topic value from an input name, returned in hex format
|
||||
|
||||
``pss.setPeerPublicKey(publickey, topic, address)``
|
||||
Register a peer's public key. This is done once for every topic that will be used with the peer. Address can be anything from 0 to 32 bytes inclusive of the peer's swarm address. The method has no return value.
|
||||
|
||||
``pss.sendAsym(publickey, topic, message)``
|
||||
Encrypts the message using the provided public key, and signs it using the node's private key. It then wraps it in an envelope containing the topic, and sends it to the network. The method has no return value.
|
||||
|
||||
``pss.setSymmetricKey(symkey, topic, address, bool decryption)``
|
||||
Register a symmetric key shared with a peer. This is done once for every topic that will be used with the peer. Address can be anything from 0 to 32 bytes inclusive of the peer's Swarm overlay address. If the fourth parameter is false, the key will not be added to the list of symmetric keys used for decryption attempts. The method returns an id used to reference the symmetric key in consecutive calls.
|
||||
|
||||
``pss.sendSym(symkeyid, topic, message)``
|
||||
Encrypts the message using the provided symmetric key, wraps it in an envelope containing the topic, and sends it to the network. The method has no return value.
|
||||
|
||||
``pss.GetSymmetricAddressHint(topic, symkeyid)``
|
||||
Return the Swarm address associated with the peer registered with the given symmetric key and topic combination. If a match is found it returns the address data in hex format.
|
||||
|
||||
``pss.GetAsymmetricAddressHint(topic, publickey)``
|
||||
Return the Swarm address associated with the peer registered with the given asymmetric key and topic combination. If a match is found it returns the address data in hex format.
|
||||
|
||||
.. note:: The following methods are used to control the optional pss handshake module. This is an advanced feature, and not required for sending and receiving messages using pss.
|
||||
|
||||
``pss.addHandshake(topic)``
|
||||
Activate handshake functionality on the specified topic. The method has no return value.
|
||||
|
||||
``pss.removeHandshake(topic)``
|
||||
Remove handshake functionality on the specified topic. The method has no return value.
|
||||
|
||||
``pss.handshake(publickey, topic, bool block, bool flush)``
|
||||
Instantiate handshake with peer, refreshing symmetric encryption keys. If parameter 3 is false the handshake will happen asynchronously. If parameter 4 is true, it will force expiry of all existing keys. The method returns a list of symmetric key ids created by the handshake. If the handshake is asynchronous, however, returned array will be empty.
|
||||
|
||||
``pss.getHandshakeKeys(publickey, topic, bool incoming, bool outgoing)``
|
||||
Returns the set of valid symmetric encryption keys for a specified peer and topic. If the incoming and outgoing parameters are set, the keys valid for the respective communcations directions are included.
|
||||
|
||||
``pss.getHandshakeKeyCapacity(symkeyid)``
|
||||
Returns the number of messages (uint16) a symmetric handshake key is valid for.
|
||||
|
||||
``pss.getHandshakePublicKey(symkeyid)``
|
||||
Returns the public key associated with the specified symmetric handshake key.
|
||||
|
||||
``pss.releaseHandshakeKey(publickey, topic, symkeyid, bool instant)``
|
||||
Invalidate the specified symmetric handshake key. Normally, the key will be kept for a grace period to allow decryption of messages not yet received at the time of release. If the instant parameter is set, this grace period is omitted, and the key removed instantaneously. This method has no return value.
|
||||
|
||||
|
||||
.. uncommentthisChequebook IPC API
|
||||
.. uncommentthis------------------------------
|
||||
|
||||
.. uncommentthisSwarm also exposes an IPC API for the chequebook offering the followng methods:
|
||||
|
||||
.. uncommentthis``chequebook.balance()``
|
||||
.. uncommentthis Returns the balance of your swap chequebook contract in wei.
|
||||
.. uncommentthis It errors if no chequebook is set.
|
||||
|
||||
.. uncommentthis``chequebook.issue(beneficiary, value)``
|
||||
.. uncommentthis Issues a cheque to beneficiary (an ethereum address) in the amount of value (given in wei). The json structure returned can be copied and sent to beneficiary who in turn can cash it using ``chequebook.cash(cheque)``.
|
||||
.. uncommentthis It errors if no chequebook is set.
|
||||
|
||||
.. uncommentthis``chequebook.cash(cheque)``
|
||||
.. uncommentthis Cashes the cheque issued. Note that anyone can cash a cheque. Its success only depends on the cheque's validity and the solvency of the issuers chequbook contract up to the amount specified in the cheque. The tranasction is paid from your bzz base account.
|
||||
.. uncommentthis Returns the transaction hash.
|
||||
.. uncommentthis It errors if no chequebook is set or if your account has insufficient funds to send the transaction.
|
||||
|
||||
.. uncommentthis``chequebook.deposit(amount)``
|
||||
.. uncommentthis Transfers funds of amount wei from your bzz base account to your swap chequebook contract.
|
||||
.. uncommentthis It errors if no chequebook is set or if your account has insufficient funds.
|
415
docs/swarm-guide/contents/architecture.rst
Normal file
@ -0,0 +1,415 @@
|
||||
.. _architecture:
|
||||
|
||||
*******************
|
||||
Architecture
|
||||
*******************
|
||||
|
||||
This chapter is aimed at developers who want to understand the underlying concepts and design of Swarm.
|
||||
In the first part we describe the Swarm network of nodes. In the second part we explain how messaging and
|
||||
data storage can use the Swarm network as a distributed preimage archive.
|
||||
|
||||
.. contents::
|
||||
|
||||
Preface
|
||||
=============
|
||||
|
||||
Swarm defines 3 crucial notions:
|
||||
|
||||
:dfn:`chunk`
|
||||
Chunks are pieces of data of limited size (max 4K), the basic unit of storage and retrieval in the Swarm. The network layer only knows about chunks and has no notion of file or collection.
|
||||
|
||||
:dfn:`reference`
|
||||
A reference is a unique identifier of a file that allows clients to retrieve and access the content. For unencrypted content the file reference is the cryptographic hash of the data and serves as its content address. This hash reference is a 32 byte hash, which is serialised with 64 hex bytes. In case of an encrypted file the reference has two equal-length components: the first 32 bytes are the content address of the encrypted asset, while the second 32 bytes are the decryption key, altogether 64 bytes, serialised as 128 hex bytes.
|
||||
|
||||
:dfn:`manifest`
|
||||
A manifest is a data structure describing file collections; they specify paths and corresponding content hashes allowing for URL based content retrieval. The :ref:`BZZ URL schemes` assumes that the content referenced in the domain is a manifest and renders the content entry whose path matches the one in the request path. Manifests can also be mapped to a filesystem directory tree, which allows for uploading and downloading directories. Finally, manifests can also be considered indexes, so it can be used to implement a simple key-value store, or alternatively, a database index. This offers the functionality of :dfn:`virtual hosting`, storing entire directories, web3 websites or primitive data structures; analogous to web2.0, with centralized hosting taken out of the equation.
|
||||
|
||||
.. image:: img/dapp-page.svg
|
||||
:alt: Example of how Swarm could serve a web page
|
||||
:width: 400
|
||||
|
||||
In this guide, content is understood very broadly in a technical sense denoting any blob of data.
|
||||
Swarm defines a specific identifier for a file. This identifier part of the reference serves as the retrieval address for the content.
|
||||
This address needs to be
|
||||
|
||||
* collision free (two different blobs of data will never map to the same identifier)
|
||||
* deterministic (same content will always receive the same identifier)
|
||||
* uniformly distributed
|
||||
|
||||
The choice of identifier in Swarm is the hierarchical Swarm hash described in :ref:`swarm_hash`.
|
||||
The properties above allow us to view hashes as addresses at which content is expected to be found.
|
||||
Since hashes can be assumed to be collision free, they are bound to one specific version of a content. Hash addressing is therefore immutable in the strong sense that you cannot even express mutable content: "changing the content changes the hash".
|
||||
|
||||
Users of the web, however, are accustomed to mutable resources, looking up domains and expect to see the most up to date version of the 'site'. Mutable resources are made possible by the ethereum name service (ENS) and Feeds.
|
||||
The ENS is a smart contract on the ethereum blockchain which enables domain owners to register a content reference to their domain.
|
||||
Using ENS for domain name resolution, the url scheme provides
|
||||
content retrieval based on mnemonic (or branded) names, much like the DNS of the world wide web, but without servers.
|
||||
Feeds is an off-chain solution for communicating updates to a resource, it offers cheaper and faster updates than ENS, yet the updates can be consolidated on ENS by any third party willing to pay for the transaction.
|
||||
|
||||
Just as content in Swarm is addressed with a 32-byte hash, so is every Swarm node in the network associated with a 32-byte hash address.
|
||||
All Swarm nodes have their own :dfn:`base address` which is derived as the (Keccak 256bit SHA3) hash of the public key of an ethereum account:
|
||||
|
||||
.. note::
|
||||
|
||||
`Swarm node address = sha3(ethereum account public key)` - the so called :dfn:`swarm base account` of the node.
|
||||
These node addresses define a location in the same address space as the data.
|
||||
|
||||
When content is uploaded to Swarm it is chopped up into pieces called chunks.
|
||||
Each chunk is accessed at the address deterministically derived from its content (using the chunk hash).
|
||||
The references of data chunks are themselves packaged into a chunk which in turn has its own hash. In this way the content gets mapped into a merkle tree. This hierarchical Swarm hash construct allows for merkle proofs for chunks within a piece of content, thus providing Swarm with integrity protected random access into (large) files (allowing for instance skipping safely in a streaming video or looking up a key in a database file).
|
||||
|
||||
Swarm implements a :dfn:`distributed preimage archive`, which is essentially a specific type of content addressed distributed hash table, where the node(s) closest to the address of a chunk do not only serve information about the content but actually host the data.
|
||||
|
||||
The viability of both hinges on the assumption that any node (uploader/requester) can 'reach' any other node (storer). This assumption is guaranteed with a special :dfn:`network topology` (called :dfn:`kademlia`), which guarantees the existence as well a maximum number of forwarding hops logarithmic in network size.
|
||||
|
||||
.. note:: There is no such thing as delete/remove in Swarm. Once data is uploaded there is no way to revoke it.
|
||||
|
||||
Nodes cache content that they pass on at retrieval, resulting in an auto scaling elastic cloud: popular (oft-accessed) content is replicated throughout the network decreasing its retrieval latency. Caching also results in a :dfn:`maximum resource utilisation` in as much as nodes will fill their dedicated storage space with data passing through them. If capacity is reached, least accessed chunks are purged by a garbage collection process. As a consequence, unpopular content will end up
|
||||
getting deleted. Storage insurance (yet to be implemented) will offer users a secure guarantee to protect important content from being purged.
|
||||
|
||||
|
||||
Overlay network
|
||||
=====================
|
||||
|
||||
|
||||
Logarithmic distance
|
||||
---------------------------------------------------
|
||||
|
||||
The distance metric :math:`MSB(x, y)` of two equal length byte sequences :math:`x` an :math:`y` is the value of the binary integer cast of :math:`x XOR y` (bitwise xor). The binary cast is big endian: most significant bit first (=MSB).
|
||||
|
||||
:math:`Proximity(x, y)` is a discrete logarithmic scaling of the MSB distance.
|
||||
It is defined as the reverse rank of the integer part of the base 2
|
||||
logarithm of the distance.
|
||||
It is calculated by counting the number of common leading zeros in the (MSB)
|
||||
binary representation of :math:`x XOR y` (0 farthest, 255 closest, 256 self).
|
||||
|
||||
.. image:: img/distance.svg
|
||||
:alt: Distance and Proximity
|
||||
|
||||
Taking the :dfn:`proximity order` relative to a fix point :math:`x` classifies the points in
|
||||
the space (byte sequences of length :math:`n`) into bins. Items in each are at
|
||||
most half as distant from :math:`x` as items in the previous bin. Given a sample of
|
||||
uniformly distributed items (a hash function over arbitrary sequence) the
|
||||
proximity scale maps onto series of subsets with cardinalities on a negative
|
||||
exponential scale.
|
||||
|
||||
It also has the property that any two addresses belonging to the same bin are at
|
||||
most half as distant from each other as they are from :math:`x`.
|
||||
|
||||
If we think of a random sample of items in the bins as connections in a network of interconnected nodes, then relative proximity can serve as the basis for local
|
||||
decisions for graph traversal where the task is to *find a route* between two
|
||||
points. Since on every hop, the finite distance halves, as long as each relevant bin is non-empty, there is
|
||||
a guaranteed constant maximum limit on the number of hops needed to reach one
|
||||
node from the other.
|
||||
|
||||
.. image:: img/topology.svg
|
||||
:alt: Kademlia topology in Swarm
|
||||
:width: 500
|
||||
|
||||
|
||||
Kademlia topology
|
||||
----------------------
|
||||
|
||||
Swarm uses the ethereum devp2p rlpx suite as the transport layer of the underlay network. This uncommon variant allows semi-stable peer connections (over TCP), with authenticated, encrypted, synchronous data streams.
|
||||
|
||||
We say that a node has :dfn:`kademlia connectivity` if (1) it is connected to at least one node for each proximity order up to (but excluding) some maximum value :math:`d` (called the :dfn:`saturation depth`) and (2) it is connected to all nodes whose proximity order relative to the node is greater or equal to :math:`d`.
|
||||
|
||||
If each point of a connected subgraph has kademlia connectivity, then we say the subgraph has :dfn:`kademlia topology`. In a graph with kademlia topology, (1) a path between any two points exists, (2) it can be found using only local decisions on each hop and (3) is guaranteed to terminate in no more steps than the depth of the destination plus one.
|
||||
|
||||
|
||||
Given a set of points uniformly distributed in the space (e.g., the results of a hash function applied to Swarm data) the proximity bins map onto a series of subsets with cardinalities on a negative exponential scale, i.e., PO bin 0 has half of the points of any random sample, PO bin 1 has one fourth, PO bin 2 one eighth, etc.
|
||||
The expected value of saturation depth in the network of :math:`N` nodes is :math:`log2(N)`. The last bin can just merge all bins deeper than the depth and is called the :dfn:`most proximate bin`.
|
||||
|
||||
Nodes in the Swarm network are identified by the hash of the ethereum address of the Swarm base account. This serves as their overlay address, the proximity order bins are calculated based on these addresses.
|
||||
Peers connected to a node define another, live kademlia table,
|
||||
where the graph edges represent devp2p rlpx connections.
|
||||
|
||||
.. image:: img/kademlia.svg
|
||||
:alt: Kademlia table for a sample node in Swarm
|
||||
:width: 600
|
||||
|
||||
If each node in a set has a saturated kademlia table of connected peers, then the nodes "live connection" graph has kademlia topology.
|
||||
The properties of a kademlia graph can be used for routing messages between nodes in a network using overlay addressing.
|
||||
In a :dfn:`forwarding kademlia` network, a message is said to be :dfn:`routable` if there exists a path from sender node to destination node through which the message could be relayed.
|
||||
In a mature subnetwork with kademlia topology every message is routable.
|
||||
A large proportion of nodes are not stably online; keeping several connected peers in their PO bins, each node can increase the chances that it can forward messages at any point in time, even if a relevant peer drops.
|
||||
|
||||
Bootstrapping and discovery
|
||||
----------------------------
|
||||
|
||||
Nodes joining a decentralised network are supposed to be naive, i.e., potentially connect via a single known peer. For this reason, the bootstrapping process will need to include a discovery component with the help of which nodes exchange information about each other.
|
||||
|
||||
The protocol is as follows:
|
||||
Initially, each node has zero as their saturation depth. Nodes keep advertising to their connected peers info about their saturation depth as it changes. If a node establishes a new connection, it notifies each of its peers about this new connection if their proximity order relative to the respective peer is not lower than the peer's advertised saturation depth (i.e., if they are sufficiently close by). The notification is always sent to each peer that shares a PO bin with the new connection. These notification about connected peers contain full overlay and underlay address information.
|
||||
Light nodes that do not wish to relay messages and do not aspire to build up a healthy kademlia are discounted.
|
||||
|
||||
As a node is being notified of new peer addresses, it stores them in a kademlia table of known peers.
|
||||
While it listens to incoming connections, it also proactively attempts to connect to nodes in order to achieve saturation: it tries to connect to each known node that is within the PO boundary of N :dfn:`nearest neighbours` called :dfn:`nearest neighbour depth` and (2) it tries to fill each bin up to the nearest neighbour depth with healthy peers. To satisfy (1) most efficiently, it attempts to connect to the peer that is most needed at any point in time. Low (far) bins are more important to fill than high (near) ones since they handle more volume. Filling an empty bin with one peer is more important than adding a new peer to a non-empty bin, since it leads to a saturated kademlia earlier. Therefore the protocol uses a bottom-up, depth-first strategy to choose a peer to connect to. Nodes that are tried but failed to get connected are retried with an exponential backoff (i.e., after a time interval that doubles after each attempt). After a certain number of attempts such nodes are no longer considered.
|
||||
|
||||
After a sufficient number of nodes are connected, a bin becomes saturated, and the bin saturation depth can increase.
|
||||
Nodes keep advertising their current saturation depth to their peers if it changes.
|
||||
As their saturation depth increases, nodes will get notified of fewer and fewer new peers (since they already know their neighbourhood). Once the node finds all their nearest neighbours and has saturated all the bins, no new peers are expected. For this reason, a node can conclude a saturated kademlia state if it receives no new peers (for some time). The node does not need to know the number of nodes in the network. In fact, some time after the node stops receiving new peer addresses, the node can effectively estimate the size of the network from the depth (depth :math:`n` implies :math:`2^n` nodes)
|
||||
|
||||
Such a network can readily be used for a forwarding-style messaging system. Swarm's PSS is based on this.
|
||||
Swarm also uses this network to implement its storage solution.
|
||||
|
||||
Distributed preimage archive
|
||||
==============================
|
||||
|
||||
:dfn:`Distributed hash tables` (DHTs) utilise an overlay network to implement a key-value store distributed over the nodes. The basic idea is that the keyspace is mapped onto the overlay address space, and information about an element in the container is to be found with nodes whose address is in the proximity of the key.
|
||||
DHTs for decentralised content addressed storage typically associate content fingerprints with a list of nodes (seeders) who can serve that content. However, the same structure can be used directly: it is not information about the location of content that is stored at the node closest to the address (fingerprint), but the content itself. We call this structure :dfn:`distributed preimage archive` (DPA).
|
||||
|
||||
.. image:: img/dpa-chunking.svg
|
||||
:alt: The DPA and chunking in Swarm
|
||||
:width: 500
|
||||
|
||||
A DPA is opinionated about which nodes store what content and this implies a few more restrictions: (1) load balancing of content among nodes is required and is accomplished by splitting content into equal sized chunks (:dfn:`chunking`); (2) there has to be a process whereby chunks get to where they are supposed to be stored (:dfn:`syncing`); and (3) since nodes do not have a say in what they store, measures of :dfn:`plausible deniability` should be employed.
|
||||
|
||||
Chunk retrieval in this design is carried out by relaying retrieve requests from a requestor node to a storer node and passing the
|
||||
retrieved chunk from the storer back to the requestor.
|
||||
|
||||
Since Swarm implements a DPA (over chunks of 4096 bytes), relaying a retrieve request to the chunk address as destination is equivalent to passing the request towards the storer node. Forwarding kademlia is able to route such retrieve requests to the neighbourhood of the chunk address. For the delivery to happen we just need to assume that each node when it forwards a retrieve request, remembers the requestors.
|
||||
Once the request reaches the storer node, delivery of the content can be initiated and consists in relaying the chunk data back to the requestor(s).
|
||||
|
||||
In this context, a chunk is retrievable for a node if the retrieve request is routable to the storer closest to the chunk address and the delivery is routable from the storer back to the requestor node.
|
||||
The success of retrievals depends on (1) the availability of strategies for finding such routes and (2) the availability of chunks with the closest nodes (syncing). The latency of request--delivery roundtrips hinges on the number of hops and the bandwidth quality of each node along the way. The delay in availability after upload depends on the efficiency of the syncing protocol.
|
||||
|
||||
Redundancy
|
||||
--------------
|
||||
|
||||
If the closest node is the only storer and drops out, there is no way to retrieve the content. This basic scenario is handled by having a set of nearest neighbours holding replicas of each chunk that is closest to any of them.
|
||||
A chunk is said to be :dfn:`redundantly retrievable` of degree math:`n` if it is retrievable and would remain so after any math:`n-1` responsible nodes leave the network.
|
||||
In the case of request forwarding failures, one can retry, or start concurrent retrieve requests.
|
||||
Such fallback options are not available if the storer nodes go down. Therefore redundancy is of major importance.
|
||||
|
||||
|
||||
The area of the fully connected neighbourhood defines an :dfn:`area of responsibility`.
|
||||
A storer node is responsible for (storing) a chunk if the chunk falls within the node's area of responsibility.
|
||||
Let us assume, then, (1) a forwarding strategy that relays requests along stable nodes and (2) a storage strategy that each node in the nearest neighbourhood (of mimimum R peers) stores all chunks within the area of responsibility. As long as these assumptions hold, each chunk is retrievable even if :math:`R-1` storer nodes drop offline simultaneously. As for (2), we still need to assume that every node in the nearest neighbour set can store each chunk.
|
||||
|
||||
Further measures of redundancy, e.g. `Erasure coding <https://en.wikipedia.org/wiki/Erasure_code>`_, will be implemented in the future.
|
||||
|
||||
|
||||
Caching and purging Storage
|
||||
----------------------------
|
||||
|
||||
Node synchronisation is the protocol that makes sure content ends up where it is queried. Since the Swarm has an address-key based retrieval protocol, content will be twice as likely be requested from a node that is one bit (one proximity bin) closer
|
||||
to the content's address. What a node stores is determined by the access count of chunks: if we reach the capacity limit for storage the oldest unaccessed chunks are removed.
|
||||
On the one hand, this is backed by an incentive system rewarding serving chunks.
|
||||
This directly translates to a motivation, that a content needs to be served with frequency X in order to make storing it profitable. On the one hand , frequency of access directly translates to storage count. On the other hand, it provides a way to combine proximity and popularity to dictate what is stored.
|
||||
|
||||
Based on distance alone (all else being equal, assuming random popularity of chunks), a node could be expected to store chunks up to a certain proximity radius.
|
||||
However, it is always possible to look for further content that is popular enough to make it worth storing. Given the power law of popularity rank and the uniform distribution of chunks in address space, one can be sure that any node can expand their storage with content where popularity of a stored chunk makes up for their distance.
|
||||
|
||||
Given absolute limits on popularity, there might be an actual upper limit on a storage capacity for a single base address that maximises profitablity. In order to efficiently utilise excess capacity, several nodes should be run in parallel.
|
||||
|
||||
This storage protocol is designed to result in an autoscaling elastic cloud where a growth in popularity automatically scales. An order of magnitude increase in popularity will result in an order of magnitude more nodes actually caching the chunk resulting in fewer hops to route the chunk, ie., a lower latency retrieval.
|
||||
|
||||
|
||||
Synchronisation
|
||||
-------------------
|
||||
|
||||
|
||||
Smart synchronisation is a protocol of distribution which makes sure that these transfers happen. Apart from access count which nodes use to determine which content to delete if capacity limit is reached, chunks also store their first entry index. This is an arbitrary monotonically increasing index, and nodes publish their current top index, so virtually they serve as timestamps of creation. This index helps keeping track what content to synchronise with a peer.
|
||||
|
||||
|
||||
When two nodes connect and they engage in synchronisation, the upstream node offers all the chunks it stores locally in a datastream per proximity order bin. To receive chunks closer to a downstream than to the upstream, downstream peer subscribes to the data stream of the PO bin it belongs to in the upstream node's kademlia table.
|
||||
If the peer connection is within nearest neighbour depth the downstream node subscribes to all PO streams that constitute the most proximate bin.
|
||||
|
||||
Nodes keep track of when they stored a chunk locally for the first time (for instance by indexing them by an ever incrementing storage count). The downstream peer is said to have completed :dfn:`history syncing` if it has (acknowledged) all the chunks of the upstream peer up from the beginning until the time the session started (up to the storage count that was the highest at the time the session
|
||||
started). Some node is said to have completed :dfn:`session syncing` with its upstream peer if it has (acknowledged) all the chunks of the upstream peer up since the session started.
|
||||
|
||||
|
||||
In order to reduce network traffic resulting from receiving chunks from multiple sources, all store requests can go via a confirmation roundtrip.
|
||||
For each peer connection in both directions, the source peer sends an :dfn:`offeredHashes` message containing a batch of hashes offered to push to the recipient. Recipient responds with a :dfn:`wantedHashes`.
|
||||
|
||||
.. image:: img/syncing-high-level.svg
|
||||
:alt: Syncing chunks in the Swarm network
|
||||
:width: 500
|
||||
|
||||
Data layer
|
||||
===================
|
||||
|
||||
There are 4 different layers of data units relevant to Swarm:
|
||||
|
||||
|
||||
* :dfn:`message`: p2p RLPx network layer. Messages are relevant for the devp2p wire protocols
|
||||
* :dfn:`chunk`: fixed size data unit of storage in the distributed preimage archive
|
||||
* :dfn:`file`: the smallest unit that is associated with a mime-type and not guaranteed to have integrity unless it is complete. This is the smallest unit semantic to the user, basically a file on a filesystem.
|
||||
* :dfn:`collection`: a mapping of paths to files is represented by the :dfn:`swarm manifest`. This layer has a mapping to file system directory tree. Given trivial routing conventions, a url can be mapped to files in a standardised way, allowing manifests to mimic site maps/routing tables. As a result, Swarm is able to act as a webserver, a virtual cloud hosting service.
|
||||
|
||||
.. index::
|
||||
manifest
|
||||
chunk
|
||||
message
|
||||
storage layer
|
||||
|
||||
The actual storage layer of Swarm consists of two main components, the :dfn:`localstore` and the :dfn:`netstore`. The local store consists of an in-memory fast cache (:dfn:`memory store`) and a persistent disk storage (:dfn:`dbstore`).
|
||||
The NetStore is extending local store to a distributed storage of Swarm and implements the :dfn:`distributed preimage archive (DPA)`.
|
||||
|
||||
.. image:: img/storage-layer.svg
|
||||
:alt: High level storage layer in Swarm
|
||||
:width: 500
|
||||
|
||||
Files
|
||||
---------
|
||||
|
||||
The :dfn:`FileStore` is the local interface for storage and retrieval of files. When a file is handed to the FileStore for storage, it chunks the document into a merkle hashtree and hands back its root key to the caller. This key can later be used to retrieve the document in question in part or whole.
|
||||
|
||||
|
||||
The component that chunks the files into the merkle tree is called the :dfn:`chunker`. Our chunker implements the :dfn:`bzzhash` algorithm which is parallellized tree hash based on an arbitrary :dfn:`chunk hash`. When the chunker is handed an I/O reader (be it a file or webcam stream), it chops the data stream into fixed sized chunks.
|
||||
The chunks are hashed using an arbitrary chunk hash (in our case the BMT hash, see below).
|
||||
If encryption is used the chunk is encrypted before hashing. The references to consecutive data chunks are concatenated and packaged into a so called :dfn:`intermediate chunk`, which in turn is encrypted and hashed and packaged into the next level of intermediate chunks.
|
||||
For unencrypted content and 32-byte chunkhash, the 4K chunk size enables 128 branches in the resulting Swarm hash tree. If we use encryption, the reference is 64-bytes, allowing for 64 branches in the Swarm hash tree.
|
||||
This recursive process of constructing the Swarm hash tree will result in a single root chunk, the chunk hash of this root chunk is the Swarm hash of the file. The reference to the document is the Swarm hash itself if the upload is unencrypted, and the Swarm hash concatenated with the decryption key of the rootchunk if the upload is encrypted.
|
||||
|
||||
When the FileStore is handed a reference for file retrieval, it calls the Chunker which hands back a seekable document reader to the caller. This is a :dfn:`lazy reader` in the sense that it retrieves parts of the underlying document only as they are being read (with some buffering similar to a video player in a browser). Given the reference, the FileStore takes the Swarm hash and using the NetStore retrieves the root chunk of the document. After decrypting it if needed, references to chunks on the next level are processed. Since data offsets can easily be mapped to a path of intermediate chunks, random access to a document is efficient and supported on the lowest level. The HTTP API offers range queries and can turn them to offset and span for the lower level API to provide integrity protected random access to files.
|
||||
|
||||
Swarm exposes the FileStore API via the :ref:`bzz-raw` URL scheme directly on the HTTP local proxy server (see :ref:`BZZ URL schemes` and :ref:`API Reference`). This API allows file upload via POST request as well as file download with GET request. Since on this level the files have no mime-type associated, in order to properly display or serve to an application, the ``content_type`` query parameter can be added to the url. This will set the proper content type in the HTTP response.
|
||||
|
||||
Manifests
|
||||
--------------
|
||||
|
||||
The Swarm :dfn:`manifest` is a structure that defines a mapping between arbitrary paths and files to handle collections. It also contains metadata associated with the collection and its objects (files). Most importantly a manifest entry specifies the media mime type of files so that browsers know how to handle them. You can think of a manifest as (1) routing table, (2) an index or (3) a directory tree, which make it possible for Swarm to implement (1) web sites, (2) databases and (3) filesystem directories.
|
||||
Manifests provide the main mechanism to allow URL based addressing in Swarm. The domain part of the URL maps onto a manifest in which the path part of the URL is looked up to arrive at a file entry to serve.
|
||||
|
||||
Manifests are currently respresented as a compacted trie (http://en.wikipedia.org/wiki/Trie) , with individual trie nodes serialised as json. The json structure has an array of :dfn:`manifest entries` minimally with a path and a reference (Swarm hash address). The path part is used for matching the URL path, the reference may point to an embedded manifest if the path is a common prefix of more than one path in the collection.
|
||||
When you retrieve a file by url, Swarm resolves the domain to a reference to a root manifest, which is recursively traversed to find the matching path.
|
||||
|
||||
The high level API to the manifests provides functionality to upload and download individual documents as files, collections (manifests) as directories. It also provides an interface to add documents to a collection on a path, delete a document from a collection. Note that deletion here only means that a new manifest is created in which the path in question is missing. There is no other notion of deletion in the Swarm.
|
||||
Swarm exposes the manifest API via the `bzz` URL scheme, see :ref:`BZZ URL schemes`.
|
||||
|
||||
These HTTP proxy API is described in detail in the :ref:`API Reference` section.
|
||||
|
||||
.. note:: In POC4, json manifests will be replaced by a serialisation scheme that enables compact path proofs, essentially asserting that a file is part of a collection that can be verified by any third party or smart contract.
|
||||
|
||||
.. index::
|
||||
manifest
|
||||
URL schemes
|
||||
API
|
||||
HTTP proxy
|
||||
|
||||
Components
|
||||
===================
|
||||
|
||||
In what follows we describe the components in more detail.
|
||||
|
||||
.. _swarm_hash:
|
||||
|
||||
Swarm Hash
|
||||
---------------
|
||||
|
||||
.. index::
|
||||
hash
|
||||
bzzhash
|
||||
|
||||
|
||||
Swarm Hash (a.k.a. `bzzhash`) is a `Merkle tree <http://en.wikipedia.org/wiki/Merkle_tree>`_ hash designed for the purpose of efficient storage and retrieval in content-addressed storage, both local and networked. While it is used in Swarm, there is nothing Swarm-specific in it and the authors recommend it as a drop-in substitute of sequential-iterative hash functions (like SHA3) whenever one is used for referencing integrity-sensitive content, as it constitutes an improvement in terms of performance and usability without compromising security.
|
||||
|
||||
In particular, it can take advantage of parallelisation for faster calculation and verification, can be used to verify the integrity of partial content without having to transmit all of it (and thereby allowing random access to files). Proofs of security to the underlying hash function carry over to Swarm Hash.
|
||||
|
||||
Swarm Hash is constructed using any chunk hash function with a generalization of Merkle's tree hash scheme. The basic unit of hashing is a :dfn:`chunk`, that can be either a :dfn:`data chunk` containing a section of the content to be hashed or an :dfn:`intermediate chunk` containing hashes of its children, which can be of either variety.
|
||||
|
||||
.. image:: img/chunk.png
|
||||
:alt: A Swarm chunk consists of 4096 bytes of the file or a sequence of 128 subtree hashes
|
||||
|
||||
Hashes of data chunks are defined as the hashes of the concatenation of the 64-bit length (in LSB-first order) of the content and the content itself. Because of the inclusion of the length, it is resistant to `length extension attacks <http://en.wikipedia.org/wiki/Length_extension_attack>`_, even if the underlying chunk hash function is not.
|
||||
|
||||
Intermediate chunks are composed of the hashes of the concatenation of the 64-bit length (in LSB-first order) of the content subsumed under this chunk followed by the references to its children (reference is either a chunk hash or chunk hash plus decryption key for encrypted content).
|
||||
|
||||
To distinguish between the two, one should compare the length of the chunk to the 64-bit number with which every chunk begins. If the chunk is exactly 8 bytes longer than this number, it is a data chunk. If it is shorter than that, it is an intermediate chunk. Otherwise, it is not a valid Swarm Hash chunk.
|
||||
|
||||
For the chunk hash we use a hashing algorithm based on a binary merkle tree over the 32-byte segments of the chunk data using a base hash function. Our choice for this base hash is the ethereum-wide used Keccak 256 SHA3 hash. For integrity protection the 8 byte span metadata is hashed together with the root of the BMT resulting in the BMT hash. BMT hash is ideal for compact solidity-friendly inclusion proofs.
|
||||
|
||||
|
||||
|
||||
Chunker
|
||||
------------
|
||||
|
||||
.. index::
|
||||
chunker
|
||||
|
||||
:dfn:`Chunker` is the interface to a component that is responsible for disassembling and assembling larger data.
|
||||
More precisely :dfn:`Splitter` disassembles, while :dfn:`Joiner` reassembles documents.
|
||||
|
||||
Our Splitter implementation is the :dfn:`pyramid` chunker that does not need the size of the file, thus is able to process live capture streams. When :dfn:`splitting` a document, the freshly created chunks are pushed to the DPA via the NetStore and calculates the Swarm hash tree to return the :dfn:`root hash` of the document that can be used as a reference when retrieving the file.
|
||||
|
||||
When :dfn:`joining` a document, the chunker needs the Swarm root hash and returns a :dfn:`lazy reader`. While joining, for chunks not found locally, network protocol requests are initiated to retrieve chunks from other nodes. If chunks are retrieved (i.e. retrieved from memory cache, disk-persisted db or via cloud based Swarm delivery from other peers in the DPA), the chunker then puts these together on demand as and where the content is being read.
|
||||
|
||||
.. index::
|
||||
chunk size
|
||||
merkle tree
|
||||
joining
|
||||
splitting
|
||||
|
||||
|
||||
|
||||
High level component description
|
||||
================================
|
||||
|
||||
In this chapter we introduce the internal software architecture of the go swarm code. It is only a high level description of the most important components. The code is documented in finer detail as comments in the codebase itself.
|
||||
|
||||
|
||||
.. image:: img/high-level-components.svg
|
||||
:alt: High level component architecture
|
||||
:width: 700
|
||||
|
||||
|
||||
|
||||
Interfaces to Swarm
|
||||
-------------------
|
||||
There are currently three entry points for communicating with a swarm node, and there is a command line interface.
|
||||
|
||||
:dfn:`Wire Protocol`
|
||||
The p2p entry point - this is the way the peers talk to each other. Protocol structure adheres to the devp2p standard and the transport is being done RLPx over TCP.
|
||||
|
||||
:dfn:`HTTP Proxy`
|
||||
The user entry point to Swarm. User operations over dapps and CLI that interact with Swarm are proxied through the HTTP interface. The API exposes methods to interact with content on Swarm.
|
||||
|
||||
:dfn:`RPC`
|
||||
Another user interface mainly used for development purposes. The user facing side of this is to be deprecated.
|
||||
|
||||
:dfn:`CLI`
|
||||
The CLI is a wrapper for the HTTP interface allowing users easy access to basic up-download functionality, content management, and it also implements some administrative tasks.
|
||||
|
||||
----
|
||||
|
||||
Structural components and key processes
|
||||
---------------------------------------
|
||||
|
||||
:dfn:`Chunker`
|
||||
When a file is submitted to the system, the input data stream is then transformed into chunks, encrypted, then hashed and stored. This results in a single root chunk reference of the data.
|
||||
|
||||
|
||||
:dfn:`Syncing process`
|
||||
Syncing is the process that deals with changes in the network when nodes join and leave, and when new content is uploaded. Push and pull syncing work together to get chunks to where they are supposed to be stored (to the local neighbourhood where they belong).
|
||||
|
||||
:dfn:`Push Sync`
|
||||
A process initiated by the uploader of content to make sure that the chunks get to the areas in the network from which they can be retrieved. Combining push-sync with tags allows users to track the status of their uploads. Push syncing is initiated upon upload of new content.
|
||||
|
||||
:dfn:`Pull Sync`
|
||||
Pull syncing is initiated by all participating nodes in order to fill up their local storage allocation in order to keep redundancy by replicating the local storage of their neighbouring peers. Pull syncing caters the need for chunk propagation towards the nearest neighbourhood. This process is responsible for maintaining a minimal redundancy level for the stored chunks.
|
||||
|
||||
|
||||
Storage module
|
||||
--------------
|
||||
|
||||
:dfn:`LocalStore`
|
||||
Provides persistent storage on each node. Provides indexes, iterators and metric storage to other components.
|
||||
|
||||
:dfn:`NetStore`
|
||||
Extends local storage with network fetching. The net store is exposed internally between the APIs in order to transparently resolve any chunk dependencies that might be needed to be satisfied from the network in order to accomodate different operations on content.
|
||||
|
||||
|
||||
:dfn:`Kademlia`
|
||||
Kademlia in the sense of Swarm has two different meanings. Firstly, Kademlia is the type of the network topology that Swarm builds upon. Secondly, within the Swarm codebase the component which manages the connections to peers over the devp2p network in order to form the Kademlia topology. Peers exchange the neccessary information about each other through a discovery protocol (which does not build upon the devp2p discovery protocol).
|
||||
|
||||
:dfn:`Feeds`
|
||||
Swarm Feeds allows a user to build an update feed about a particular topic without resorting to ENS on each update. The update scheme is built on swarm chunks with chunk keys following a predictable, versionable pattern. A Feed is defined as the series of updates of a specific user about a particular topic.
|
||||
|
||||
Communication layer
|
||||
-------------------
|
||||
|
||||
:dfn:`PSS`
|
||||
A messaging subsystem which builds upon the Kademlia topology to provide application level messaging (eg. chat dapps) and is also used for Push-sync.
|
||||
|
388
docs/swarm-guide/contents/conf.py
Normal file
@ -0,0 +1,388 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# swarm documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sun Dec 20 19:19:54 2015.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import shlex
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
# 'sphinx.ext.intersphinx',
|
||||
# 'sphinx.ext.todo',
|
||||
'sphinx.ext.mathjax',
|
||||
'sphinx_tabs.tabs',
|
||||
# 'sphinx.ext.ifconfig',
|
||||
# 'sphinxcontrib.httpdomain',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Swarm'
|
||||
# copyright = u'2015, ΞTHΞЯSPHΞЯΞ'
|
||||
copyright = u'2018, Ethersphere'
|
||||
author = u'viktor trón, aron fischer, nick johnson, dániel a. nagy, zsolt felföldi, stan destinatis'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'0.4'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'0.4'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = [
|
||||
'_build',
|
||||
]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
#s
|
||||
# html_theme = "classic"
|
||||
html_theme_options = {
|
||||
# "rightsidebar": "true",
|
||||
# "relbarbgcolor": "black",
|
||||
}
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = ["../docs/_themes/" ]
|
||||
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
# html_logo = "img/swarm-logo.jpg"
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
|
||||
#html_search_language = 'en'
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# Now only 'ja' uses this config value
|
||||
#html_search_options = {'type': 'default'}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
#html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'swarmdoc'
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
'papersize': 'a4paper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
'pointsize': '10pt',
|
||||
#'pointsize': '11pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
'fncychap': '',
|
||||
'fontpkg': '\\usepackage{palatino}'
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'swarm.tex', u'swarm documentation',
|
||||
u'viktor trón, áron fischer, nick johnson, dániel a. nagy, zsolt felföldi', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
latex_logo = "img/swarm-logo.jpg"
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'swarm', u'swarm Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'swarm', u'swarm Documentation',
|
||||
author, 'swarm', 'ethereum content storage distribution',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
# -- Options for Epub output ----------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = project
|
||||
epub_author = author
|
||||
epub_publisher = author
|
||||
epub_copyright = copyright
|
||||
|
||||
# The basename for the epub file. It defaults to the project name.
|
||||
#epub_basename = project
|
||||
|
||||
# The HTML theme for the epub output. Since the default themes are not optimized
|
||||
# for small screen space, using the same theme for HTML and epub output is
|
||||
# usually not wise. This defaults to 'epub', a theme designed to save visual
|
||||
# space.
|
||||
#epub_theme = 'epub'
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or 'en' if the language is not set.
|
||||
#epub_language = ''
|
||||
|
||||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||
#epub_scheme = ''
|
||||
|
||||
# The unique identifier of the text. This can be a ISBN number
|
||||
# or the project homepage.
|
||||
#epub_identifier = ''
|
||||
|
||||
# A unique identification for the text.
|
||||
#epub_uid = ''
|
||||
|
||||
# A tuple containing the cover image and cover page html template filenames.
|
||||
#epub_cover = ()
|
||||
|
||||
# A sequence of (type, uri, title) tuples for the guide element of content.opf.
|
||||
#epub_guide = ()
|
||||
|
||||
# HTML files that should be inserted before the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#epub_pre_files = []
|
||||
|
||||
# HTML files shat should be inserted after the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#epub_post_files = []
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
epub_exclude_files = ['search.html']
|
||||
|
||||
# The depth of the table of contents in toc.ncx.
|
||||
#epub_tocdepth = 3
|
||||
|
||||
# Allow duplicate toc entries.
|
||||
#epub_tocdup = True
|
||||
|
||||
# Choose between 'default' and 'includehidden'.
|
||||
#epub_tocscope = 'default'
|
||||
|
||||
# Fix unsupported image types using the Pillow.
|
||||
#epub_fix_images = False
|
||||
|
||||
# Scale large images.
|
||||
#epub_max_image_width = 0
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#epub_show_urls = 'inline'
|
||||
|
||||
# If false, no index is generated.
|
||||
#epub_use_index = True
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'https://docs.python.org/': None}
|
||||
|
||||
|
||||
http_index_localname = "SWARM HTTP API"
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_stylesheet("theme_overrides.css")
|
96
docs/swarm-guide/contents/configuration.rst
Normal file
@ -0,0 +1,96 @@
|
||||
******************************
|
||||
Configuration
|
||||
******************************
|
||||
|
||||
.. _configuration:
|
||||
|
||||
Command line options for swarm
|
||||
====================================
|
||||
|
||||
The ``swarm`` executable supports the following configuration options:
|
||||
|
||||
* Configuration file
|
||||
* Environment variables
|
||||
* Command line
|
||||
|
||||
Options provided via command line override options from the environment variables, which will override options in the config file. If an option is not explicitly provided, a default will be chosen.
|
||||
|
||||
In order to keep the set of flags and variables manageable, only a subset of all available configuration options are available via command line and environment variables. Some are only available through a TOML configuration file.
|
||||
|
||||
.. note:: Swarm reuses code from ethereum, specifically some p2p networking protocol and other common parts. To this end, it accepts a number of environment variables which are actually from the ``geth`` environment. Refer to the geth documentation for reference on these flags.
|
||||
|
||||
This is the list of flags inherited from ``geth``:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
--identity
|
||||
--bootnodes
|
||||
--datadir
|
||||
--keystore
|
||||
--port
|
||||
--nodiscover
|
||||
--v5disc
|
||||
--netrestrict
|
||||
--nodekey
|
||||
--nodekeyhex
|
||||
--maxpeers
|
||||
--nat
|
||||
--ipcdisable
|
||||
--ipcpath
|
||||
--password
|
||||
|
||||
Config File
|
||||
=============
|
||||
|
||||
.. note:: ``swarm`` can be executed with the ``dumpconfig`` command, which prints a default configuration to STDOUT, and thus can be redirected to a file as a template for the config file.
|
||||
|
||||
A TOML configuration file is organized in sections. The below list of available configuration options is organized according to these sections. The sections correspond to `Go` modules, so need to be respected in order for file configuration to work properly. See `<https://github.com/naoina/toml>`_ for the TOML parser and encoder library for Golang, and `<https://github.com/toml-lang/toml>`_ for further information on TOML.
|
||||
|
||||
To run Swarm with a config file, use:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ swarm --config /path/to/config/file.toml
|
||||
|
||||
General configuration parameters
|
||||
================================
|
||||
|
||||
.. csv-table::
|
||||
:header: "Config file", "Command-line flag", "Environment variable", "Default value", "Description"
|
||||
:widths: 10, 5, 5, 15, 55
|
||||
|
||||
"n/a","--config","n/a","n/a","Path to config file in TOML format"
|
||||
"n/a","--bzzapi","n/a","http://127.0.0.1:8500","Swarm HTTP endpoint"
|
||||
"BootNodes","--bootnodes","SWARM_BOOTNODES","","Boot nodes"
|
||||
"BzzAccount","--bzzaccount","SWARM_ACCOUNT", "","Swarm account key"
|
||||
"BzzKey","n/a","n/a", "n/a","Swarm node base address (:math:`hash(PublicKey)hash(PublicKey))`. This is used to decide storage based on radius and routing by kademlia."
|
||||
"Contract","--chequebook","SWARM_CHEQUEBOOK_ADDR","0x0","Swap chequebook contract address"
|
||||
"Cors","--corsdomain","SWARM_CORS", "","Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')"
|
||||
"n/a","--debug","n/a","n/a","Prepends log messages with call-site location (file and line number)"
|
||||
"n/a","--defaultpath","n/a","n/a","path to file served for empty url path (none)"
|
||||
"n/a","--delivery-skip-check","SWARM_DELIVERY_SKIP_CHECK","false","Skip chunk delivery check (default false)"
|
||||
"EnsApi","--ens-api","SWARM_ENS_API","<$GETH_DATADIR>/geth.ipc","Ethereum Name Service API address"
|
||||
"EnsRoot","--ens-addr","SWARM_ENS_ADDR", "ens.TestNetAddress","Ethereum Name Service contract address"
|
||||
"ListenAddr","--httpaddr","SWARM_LISTEN_ADDR", "127.0.0.1","Swarm listen address"
|
||||
"n/a","--manifest value","n/a","true","Automatic manifest upload (default true)"
|
||||
"n/a","--mime value","n/a","n/a","Force mime type on upload"
|
||||
"NetworkId","--bzznetworkid","SWARM_NETWORK_ID","3","Network ID"
|
||||
"Path","--datadir","GETH_DATADIR","<$GETH_DATADIR>/swarm","Path to the geth configuration directory"
|
||||
"Port","--bzzport","SWARM_PORT", "8500","Port to run the http proxy server"
|
||||
"PublicKey","n/a","n/a", "n/a","Public key of swarm base account"
|
||||
"n/a","--recursive","n/a", "false","Upload directories recursively (default false)"
|
||||
"n/a","--stdin","","n/a","Reads data to be uploaded from stdin"
|
||||
"n/a","--store.path value","SWARM_STORE_PATH","<$GETH_ENV_DIR>/swarm/bzz-<$BZZ_KEY>/chunks","Path to leveldb chunk DB"
|
||||
"n/a","--store.size value","SWARM_STORE_CAPACITY","5000000","Number of chunks (5M is roughly 20-25GB) (default 5000000)]"
|
||||
"n/a","--store.cache.size value","SWARM_STORE_CACHE_CAPACITY","5000","Number of recent chunks cached in memory (default 5000)"
|
||||
"n/a","--sync-update-delay value","SWARM_ENV_SYNC_UPDATE_DELAY","","Duration for sync subscriptions update after no new peers are added (default 15s)"
|
||||
"SwapApi","--swap-api","SWARM_SWAP_API","","URL of the Ethereum API provider to use to settle SWAP payments"
|
||||
"SwapEnabled","--swap","SWARM_SWAP_ENABLE","false","Enable SWAP"
|
||||
"SyncDisabled","--nosync","SWARM_ENV_SYNC_DISABLE","false","Disable Swarm node synchronization"
|
||||
"n/a","--verbosity value","n/a","3","Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail"
|
||||
"n/a","--ws","n/a","false","Enable the WS-RPC server"
|
||||
"n/a","--wsaddr value","n/a","localhost","WS-RPC server listening interface"
|
||||
"n/a","--wsport value","n/a","8546","WS-RPC server listening port"
|
||||
"n/a","--wsapi value","n/a","n/a","API's offered over the WS-RPC interface"
|
||||
"n/a","--wsorigins value","n/a","n/a","Origins from which to accept websockets requests"
|
||||
"n/a","n/a","SWARM_AUTO_DEFAULTPATH","false","Toggle automatic manifest default path on recursive uploads (looks for index.html)"
|
359
docs/swarm-guide/contents/gettingstarted.rst
Normal file
@ -0,0 +1,359 @@
|
||||
.. _Getting Started:
|
||||
|
||||
******************************
|
||||
Getting started
|
||||
******************************
|
||||
|
||||
The first thing to do is to start up your Swarm node and connect it to the Swarm network.
|
||||
|
||||
Running Swarm
|
||||
=============
|
||||
|
||||
To start a basic Swarm node you must have both ``geth`` and ``swarm`` installed on your machine. You can find the relevant instructions in the `Installation and Updates <./installation.html>`_ section. ``geth`` is the go-ethereum client, you can read up on it in the `Ethereum Homestead documentation <http://ethdocs.org/en/latest/ethereum-clients/go-ethereum/index.html>`_.
|
||||
|
||||
To start Swarm you need an Ethereum account. You can create a new account in ``geth`` by running the following command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth account new
|
||||
|
||||
You will be prompted for a password:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
Your new account is locked with a password. Please give a password. Do not forget this password.
|
||||
Passphrase:
|
||||
Repeat passphrase:
|
||||
|
||||
Once you have specified the password, the output will be the Ethereum address representing that account. For example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
Address: {2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1}
|
||||
|
||||
Using this account, connect to Swarm with
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzaccount <your-account-here>
|
||||
# in our example
|
||||
$ swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1
|
||||
|
||||
(You should replace ``2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1`` with your account address key).
|
||||
|
||||
.. important::
|
||||
|
||||
**Remember your password.** There is no *forgot my password* option for ``swarm`` and ``geth``.
|
||||
|
||||
Verifying that your local Swarm node is running
|
||||
-----------------------------------------------
|
||||
|
||||
When running, ``swarm`` is accessible through an HTTP API on port 8500. Confirm that it is up and running by pointing your browser to http://localhost:8500 (You should see a Swarm search box.)
|
||||
|
||||
Interacting with Swarm
|
||||
======================
|
||||
|
||||
.. _3.2:
|
||||
|
||||
The easiest way to access Swarm through the command line, or through the `Geth JavaScript Console <http://ethdocs.org/en/latest/account-management.html>`_ by attaching the console to a running swarm node. ``$BZZKEY$`` refers to your account address key.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Linux
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzaccount $BZZKEY
|
||||
|
||||
And, in a new terminal window:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth attach $HOME/.ethereum/bzzd.ipc
|
||||
|
||||
.. group-tab:: macOS
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzaccount $BZZKEY
|
||||
|
||||
And, in a new terminal window:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth attach $HOME/Library/Ethereum/bzzd.ipc
|
||||
|
||||
.. group-tab:: Windows
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzaccount $BZZKEY
|
||||
|
||||
And, in a new terminal window:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth attach \\.\pipe\bzzd.ipc
|
||||
|
||||
|
||||
Swarm is fully compatible with Geth Console commands. For example, you can list your peers using ``admin.peers``, add a peer using ``admin.addPeer``, and so on.
|
||||
|
||||
You can use Swarm with CLI flags and environment variables. See a full list in the `Configuration <./configuration.html>`_ .
|
||||
|
||||
.. _connect-ens:
|
||||
|
||||
How do I enable ENS name resolution?
|
||||
=====================================
|
||||
|
||||
The `Ethereum Name Service <http://ens.readthedocs.io/en/latest/introduction.html>`_ (ENS) is the Ethereum equivalent of DNS in the classic web. It is based on a suite of smart contracts running on the *Ethereum mainnet*.
|
||||
|
||||
In order to use **ENS** to resolve names to swarm content hashes, ``swarm`` has to connect to a ``geth`` instance that is connected to the *Ethereum mainnet*. This is done using the ``--ens-api`` flag.
|
||||
|
||||
First you must start your geth node and establish connection with Ethereum main network with the following command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth
|
||||
|
||||
for a full geth node, or
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth --syncmode=light
|
||||
|
||||
for light client mode.
|
||||
|
||||
.. note::
|
||||
|
||||
**Syncing might take a while.** When you use the light mode, you don't have to sync the node before it can be used to answer ENS queries. However, please note that light mode is still an experimental feature.
|
||||
|
||||
After the connection is established, open another terminal window and connect to Swarm:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Linux
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --ens-api $HOME/.ethereum/geth.ipc \
|
||||
--bzzaccount $BZZKEY
|
||||
|
||||
.. group-tab:: macOS
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --ens-api $HOME/Library/Ethereum/geth.ipc \
|
||||
--bzzaccount $BZZKEY
|
||||
|
||||
.. group-tab:: Windows
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --ens-api \\.\pipe\geth.ipc \
|
||||
--bzzaccount $BZZKEY
|
||||
|
||||
|
||||
Verify that this was successful by pointing your browser to http://localhost:8500/bzz:/theswarm.eth/
|
||||
|
||||
Using Swarm together with the testnet ENS
|
||||
------------------------------------------
|
||||
|
||||
It is also possible to use the Ropsten ENS test registrar for name resolution instead of the Ethereum main .eth ENS on mainnet.
|
||||
|
||||
Run a geth node connected to the Ropsten testnet
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth --testnet
|
||||
|
||||
Then launch the ``swarm``; connecting it to the geth node (``--ens-api``).
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Linux
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --ens-api $HOME/.ethereum/geth/testnet/geth.ipc \
|
||||
--bzzaccount $BZZKEY
|
||||
|
||||
.. group-tab:: macOS
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --ens-api $HOME/Library/Ethereum/geth/testnet/geth.ipc \
|
||||
--bzzaccount $BZZKEY
|
||||
|
||||
.. group-tab:: Windows
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --ens-api \\.\pipe\geth.ipc \
|
||||
--bzzaccount $BZZKEY
|
||||
|
||||
|
||||
Swarm will automatically use the ENS deployed on Ropsten.
|
||||
|
||||
For other ethereum blockchains and other deployments of the ENS contracts, you can specify the contract addresses manually. For example the following command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --ens-api eth:<contract 1>@/home/user/.ethereum/geth.ipc \
|
||||
--ens-api test:<contract 2>@ws:<address 1> \
|
||||
--ens-api <contract 3>@ws:<address 2>
|
||||
|
||||
Will use the ``geth.ipc`` to resolve ``.eth`` names using the contract at address ``<contract 1>`` and it will use ``ws:<address 1>`` to resolve ``.test`` names using the contract at address ``<contract 2>``. For all other names it will use the ENS contract at address ``<contract 3>`` on ``ws:<address 2>``.
|
||||
|
||||
Using an external ENS source
|
||||
----------------------------
|
||||
|
||||
.. important::
|
||||
|
||||
Take care when using external sources of information. By doing so you are trusting someone else to be truthful. Using an external ENS source may make you vulnerable to man-in-the-middle attacks. It is only recommended for test and development environments.
|
||||
|
||||
Maintaining a fully synced Ethereum node comes with certain hardware and bandwidth constraints, and can be tricky to achieve. Also, light client mode, where syncing is not necessary, is still experimental.
|
||||
|
||||
An alternative solution for development purposes is to connect to an external node that you trust, and that offers the necessary functionality through HTTP.
|
||||
|
||||
If the external node is running on IP 12.34.56.78 port 8545, the command would be:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --ens-api http://12.34.45.78:8545
|
||||
|
||||
You can also use ``https``. But keep in mind that Swarm *does not validate the certificate*.
|
||||
|
||||
|
||||
Alternative modes
|
||||
=================
|
||||
|
||||
Below are examples on ways to run ``swarm`` beyond just the default network. You can instruct Swarm using the geth command line interface or use the geth javascript console.
|
||||
|
||||
Swarm in singleton mode (no peers)
|
||||
------------------------------------
|
||||
|
||||
If you **don't** want your swarm node to connect to any existing networks, you can provide it with a custom network identifier using ``--bzznetworkid`` with a random large number.
|
||||
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Linux
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzaccount $BZZKEY \
|
||||
--datadir $HOME/.ethereum \
|
||||
--ens-api $HOME/.ethereum/geth.ipc \
|
||||
--bzznetworkid <random number between 15 and 256>
|
||||
|
||||
.. group-tab:: macOS
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzaccount $BZZKEY \
|
||||
--datadir $HOME/Library/Ethereum/ \
|
||||
--ens-api $HOME/Library/Ethereum/geth.ipc \
|
||||
--bzznetworkid <random number between 15 and 256>
|
||||
|
||||
.. group-tab:: Windows
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzaccount $BZZKEY \
|
||||
--datadir %HOMEPATH%\AppData\Roaming\Ethereum \
|
||||
--ens-api \\.\pipe\geth.ipc \
|
||||
--bzznetworkid <random number between 15 and 256>
|
||||
|
||||
Adding enodes manually
|
||||
------------------------
|
||||
|
||||
By default, Swarm will automatically seek out peers in the network.
|
||||
|
||||
Additionally you can manually start off the connection process by adding one or more peers using the ``admin.addPeer`` console command.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Linux
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth --exec='admin.addPeer("ENODE")' attach $HOME/.ethereum/bzzd.ipc
|
||||
|
||||
.. group-tab:: macOS
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth --exec='admin.addPeer("ENODE")' attach $HOME/Library/Ethereum/bzzd.ipc
|
||||
|
||||
.. group-tab:: Windows
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth --exec='admin.addPeer("ENODE")' attach \\.\pipe\bzzd.ipc
|
||||
|
||||
(You can also do this in the Geth Console, as seen in Section 3.2_.)
|
||||
|
||||
.. note::
|
||||
|
||||
When you stop a node, all peer connections will be saved. When you start again, the node will try to reconnect to those peers automatically.
|
||||
|
||||
Where ENODE is the enode record of a swarm node. Such a record looks like the following:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
enode://01f7728a1ba53fc263bcfbc2acacc07f08358657070e17536b2845d98d1741ec2af00718c79827dfdbecf5cfcd77965824421508cc9095f378eb2b2156eb79fa@1.2.3.4:30399
|
||||
|
||||
The enode of your swarm node can be accessed using ``geth`` connected to ``bzzd.ipc``
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Linux
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth --exec "admin.nodeInfo.enode" attach $HOME/.ethereum/bzzd.ipc
|
||||
|
||||
.. group-tab:: macOS
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth --exec "admin.nodeInfo.enode" attach $HOME/Library/Ethereum/bzzd.ipc
|
||||
|
||||
.. group-tab:: Windows
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ geth --exec "admin.nodeInfo.enode" attach \\.\pipe\bzzd.ipc
|
||||
|
||||
|
||||
.. note::
|
||||
Note how ``geth`` is used for two different purposes here: You use it to run an Ethereum Mainnet node for ENS lookups. But you also use it to "attach" to the Swarm node to send commands to it.
|
||||
|
||||
Connecting to the public Swarm cluster
|
||||
--------------------------------------
|
||||
|
||||
By default Swarm connects to the public Swarm testnet operated by the Ethereum Foundation and other contributors.
|
||||
|
||||
The nodes the team maintains function as a free-to-use public access gateway to Swarm, so that users can experiment with Swarm without the need to run a local node. To download data through the gateway use the ``https://swarm-gateways.net/bzz:/<address>/`` URL.
|
||||
|
||||
Metrics reporting
|
||||
------------------
|
||||
|
||||
Swarm uses the `go-metrics` library for metrics collection. You can set your node to collect metrics and push them to an influxdb database (called `metrics` by default) with the default settings. Tracing is also supported. An example of a default configuration is given below:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzaccount <bzzkey> \
|
||||
--debug \
|
||||
--metrics \
|
||||
--metrics.influxdb.export \
|
||||
--metrics.influxdb.endpoint "http://localhost:8086" \
|
||||
--metrics.influxdb.username "user" \
|
||||
--metrics.influxdb.password "pass" \
|
||||
--metrics.influxdb.database "metrics" \
|
||||
--metrics.influxdb.host.tag "localhost" \
|
||||
--verbosity 4 \
|
||||
--tracing \
|
||||
--tracing.endpoint=jaeger:6831 \
|
||||
--tracing.svc myswarm
|
||||
|
BIN
docs/swarm-guide/contents/img/bmt.png
Normal file
After Width: | Height: | Size: 111 KiB |
BIN
docs/swarm-guide/contents/img/chunk.png
Normal file
After Width: | Height: | Size: 42 KiB |
658
docs/swarm-guide/contents/img/dapp-page.svg
Normal file
After Width: | Height: | Size: 957 KiB |
583
docs/swarm-guide/contents/img/distance.svg
Normal file
@ -0,0 +1,583 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.2" width="215.9mm" height="279.4mm" viewBox="0 0 21590 27940" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
|
||||
<defs class="ClipPathGroup">
|
||||
<clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="21590" height="27940"/>
|
||||
</clipPath>
|
||||
<clipPath id="presentation_clip_path_shrink" clipPathUnits="userSpaceOnUse">
|
||||
<rect x="21" y="27" width="21547" height="27885"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<defs>
|
||||
<font id="EmbeddedFont_1" horiz-adv-x="2048">
|
||||
<font-face font-family="Liberation Sans embedded" units-per-em="2048" font-weight="normal" font-style="normal" ascent="1859" descent="433"/>
|
||||
<missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
|
||||
<glyph unicode="–" horiz-adv-x="1142" d="M 0,451 L 0,588 1138,588 1138,451 0,451 Z"/>
|
||||
<glyph unicode="y" horiz-adv-x="1032" d="M 191,-425 C 142,-425 100,-421 67,-414 L 67,-279 C 92,-283 120,-285 151,-285 263,-285 352,-203 417,-38 L 434,5 5,1082 197,1082 425,484 C 428,475 432,464 437,451 442,438 457,394 482,320 507,246 521,205 523,196 L 593,393 830,1082 1020,1082 604,0 C 559,-115 518,-201 479,-258 440,-314 398,-356 351,-384 304,-411 250,-425 191,-425 Z"/>
|
||||
<glyph unicode="x" horiz-adv-x="995" d="M 801,0 L 510,444 217,0 23,0 408,556 41,1082 240,1082 510,661 778,1082 979,1082 612,558 1002,0 801,0 Z"/>
|
||||
<glyph unicode="w" horiz-adv-x="1501" d="M 1174,0 L 965,0 776,765 740,934 C 734,904 725,861 712,805 699,748 631,480 508,0 L 300,0 -3,1082 175,1082 358,347 C 363,331 377,265 401,149 L 418,223 644,1082 837,1082 1026,339 1072,149 1103,288 1308,1082 1484,1082 1174,0 Z"/>
|
||||
<glyph unicode="u" horiz-adv-x="884" d="M 314,1082 L 314,396 C 314,325 321,269 335,230 349,191 371,162 402,145 433,128 478,119 537,119 624,119 692,149 742,208 792,267 817,350 817,455 L 817,1082 997,1082 997,231 C 997,105 999,28 1003,0 L 833,0 C 832,3 832,12 831,27 830,42 830,59 829,78 828,97 826,132 825,185 L 822,185 C 781,110 733,58 679,27 624,-4 557,-20 476,-20 357,-20 271,10 216,69 161,128 133,225 133,361 L 133,1082 314,1082 Z"/>
|
||||
<glyph unicode="t" horiz-adv-x="534" d="M 554,8 C 495,-8 434,-16 372,-16 228,-16 156,66 156,229 L 156,951 31,951 31,1082 163,1082 216,1324 336,1324 336,1082 536,1082 536,951 336,951 336,268 C 336,216 345,180 362,159 379,138 408,127 450,127 474,127 509,132 554,141 L 554,8 Z"/>
|
||||
<glyph unicode="s" horiz-adv-x="903" d="M 950,299 C 950,197 912,118 835,63 758,8 650,-20 511,-20 376,-20 273,2 200,47 127,91 79,160 57,254 L 216,285 C 231,227 263,185 311,158 359,131 426,117 511,117 602,117 669,131 712,159 754,187 775,229 775,285 775,328 760,362 731,389 702,416 654,438 589,455 L 460,489 C 357,516 283,542 240,568 196,593 162,624 137,661 112,698 100,743 100,796 100,895 135,970 206,1022 276,1073 378,1099 513,1099 632,1099 727,1078 798,1036 868,994 912,927 931,834 L 769,814 C 759,862 732,899 689,925 645,950 586,963 513,963 432,963 372,951 333,926 294,901 275,864 275,814 275,783 283,758 299,738 315,718 339,701 370,687 401,673 467,654 568,629 663,605 732,583 774,563 816,542 849,520 874,495 898,470 917,442 930,410 943,377 950,340 950,299 Z"/>
|
||||
<glyph unicode="r" horiz-adv-x="525" d="M 142,0 L 142,830 C 142,906 140,990 136,1082 L 306,1082 C 311,959 314,886 314,861 L 318,861 C 347,954 380,1017 417,1051 454,1085 507,1102 575,1102 599,1102 623,1099 648,1092 L 648,927 C 624,934 592,937 552,937 477,937 420,905 381,841 342,776 322,684 322,564 L 322,0 142,0 Z"/>
|
||||
<glyph unicode="p" horiz-adv-x="930" d="M 1053,546 C 1053,169 920,-20 655,-20 488,-20 376,43 319,168 L 314,168 C 317,163 318,106 318,-2 L 318,-425 138,-425 138,861 C 138,972 136,1046 132,1082 L 306,1082 C 307,1079 308,1070 309,1054 310,1037 312,1012 314,978 315,944 316,921 316,908 L 320,908 C 352,975 394,1024 447,1055 500,1086 569,1101 655,1101 788,1101 888,1056 954,967 1020,878 1053,737 1053,546 Z M 864,542 C 864,693 844,800 803,865 762,930 698,962 609,962 538,962 482,947 442,917 401,887 371,840 350,777 329,713 318,630 318,528 318,386 341,281 386,214 431,147 505,113 607,113 696,113 762,146 803,212 844,277 864,387 864,542 Z"/>
|
||||
<glyph unicode="o" horiz-adv-x="976" d="M 1053,542 C 1053,353 1011,212 928,119 845,26 724,-20 565,-20 407,-20 288,28 207,125 126,221 86,360 86,542 86,915 248,1102 571,1102 736,1102 858,1057 936,966 1014,875 1053,733 1053,542 Z M 864,542 C 864,691 842,800 798,868 753,935 679,969 574,969 469,969 393,935 346,866 299,797 275,689 275,542 275,399 298,292 345,221 391,149 464,113 563,113 671,113 748,148 795,217 841,286 864,395 864,542 Z"/>
|
||||
<glyph unicode="n" horiz-adv-x="884" d="M 825,0 L 825,686 C 825,757 818,813 804,852 790,891 768,920 737,937 706,954 661,963 602,963 515,963 447,933 397,874 347,815 322,732 322,627 L 322,0 142,0 142,851 C 142,977 140,1054 136,1082 L 306,1082 C 307,1079 307,1070 308,1055 309,1040 310,1024 311,1005 312,986 313,950 314,897 L 317,897 C 358,972 406,1025 461,1056 515,1087 582,1102 663,1102 782,1102 869,1073 924,1014 979,955 1006,857 1006,721 L 1006,0 825,0 Z"/>
|
||||
<glyph unicode="m" horiz-adv-x="1455" d="M 768,0 L 768,686 C 768,791 754,863 725,903 696,943 645,963 570,963 493,963 433,934 388,875 343,816 321,734 321,627 L 321,0 142,0 142,851 C 142,977 140,1054 136,1082 L 306,1082 C 307,1079 307,1070 308,1055 309,1040 310,1024 311,1005 312,986 313,950 314,897 L 317,897 C 356,974 400,1027 450,1057 500,1087 561,1102 633,1102 715,1102 780,1086 828,1053 875,1020 908,968 927,897 L 930,897 C 967,970 1013,1022 1066,1054 1119,1086 1183,1102 1258,1102 1367,1102 1447,1072 1497,1013 1546,954 1571,856 1571,721 L 1571,0 1393,0 1393,686 C 1393,791 1379,863 1350,903 1321,943 1270,963 1195,963 1116,963 1055,934 1012,876 968,817 946,734 946,627 L 946,0 768,0 Z"/>
|
||||
<glyph unicode="l" horiz-adv-x="185" d="M 138,0 L 138,1484 318,1484 318,0 138,0 Z"/>
|
||||
<glyph unicode="i" horiz-adv-x="194" d="M 137,1312 L 137,1484 317,1484 317,1312 137,1312 Z M 137,0 L 137,1082 317,1082 317,0 137,0 Z"/>
|
||||
<glyph unicode="h" horiz-adv-x="875" d="M 317,897 C 356,968 402,1020 457,1053 511,1086 580,1102 663,1102 780,1102 867,1073 923,1015 978,956 1006,858 1006,721 L 1006,0 825,0 825,686 C 825,762 818,819 804,856 790,893 767,920 735,937 703,954 659,963 602,963 517,963 450,934 399,875 348,816 322,737 322,638 L 322,0 142,0 142,1484 322,1484 322,1098 C 322,1057 321,1015 319,972 316,929 315,904 314,897 L 317,897 Z"/>
|
||||
<glyph unicode="g" horiz-adv-x="930" d="M 548,-425 C 430,-425 336,-402 266,-356 196,-309 151,-243 131,-158 L 312,-132 C 324,-182 351,-220 392,-248 433,-274 486,-288 553,-288 732,-288 822,-183 822,27 L 822,201 820,201 C 786,132 739,80 680,45 621,10 551,-8 472,-8 339,-8 242,36 180,124 117,212 86,350 86,539 86,730 120,872 187,963 254,1054 355,1099 492,1099 569,1099 635,1082 692,1047 748,1012 791,962 822,897 L 824,897 C 824,917 825,952 828,1001 831,1050 833,1077 836,1082 L 1007,1082 C 1003,1046 1001,971 1001,858 L 1001,31 C 1001,-273 850,-425 548,-425 Z M 822,541 C 822,629 810,705 786,769 762,832 728,881 685,915 641,948 591,965 536,965 444,965 377,932 335,865 293,798 272,690 272,541 272,393 292,287 331,222 370,157 438,125 533,125 590,125 640,142 684,175 728,208 762,256 786,319 810,381 822,455 822,541 Z"/>
|
||||
<glyph unicode="e" horiz-adv-x="976" d="M 276,503 C 276,379 302,283 353,216 404,149 479,115 578,115 656,115 719,131 766,162 813,193 844,233 861,281 L 1019,236 C 954,65 807,-20 578,-20 418,-20 296,28 213,123 129,218 87,360 87,548 87,727 129,864 213,959 296,1054 416,1102 571,1102 889,1102 1048,910 1048,527 L 1048,503 276,503 Z M 862,641 C 852,755 823,838 775,891 727,943 658,969 568,969 481,969 412,940 361,882 310,823 282,743 278,641 L 862,641 Z"/>
|
||||
<glyph unicode="d" horiz-adv-x="930" d="M 821,174 C 788,105 744,55 689,25 634,-5 565,-20 484,-20 347,-20 247,26 183,118 118,210 86,349 86,536 86,913 219,1102 484,1102 566,1102 634,1087 689,1057 744,1027 788,979 821,914 L 823,914 821,1035 821,1484 1001,1484 1001,223 C 1001,110 1003,36 1007,0 L 835,0 C 833,11 831,35 829,74 826,113 825,146 825,174 L 821,174 Z M 275,542 C 275,391 295,282 335,217 375,152 440,119 530,119 632,119 706,154 752,225 798,296 821,405 821,554 821,697 798,802 752,869 706,936 633,969 532,969 441,969 376,936 336,869 295,802 275,693 275,542 Z"/>
|
||||
<glyph unicode="c" horiz-adv-x="894" d="M 275,546 C 275,402 298,295 343,226 388,157 457,122 548,122 612,122 666,139 709,174 752,209 778,262 788,334 L 970,322 C 956,218 912,135 837,73 762,11 668,-20 553,-20 402,-20 286,28 207,124 127,219 87,359 87,542 87,724 127,863 207,959 287,1054 402,1102 551,1102 662,1102 754,1073 827,1016 900,959 945,880 964,779 L 779,765 C 770,825 746,873 708,908 670,943 616,961 546,961 451,961 382,929 339,866 296,803 275,696 275,546 Z"/>
|
||||
<glyph unicode="b" horiz-adv-x="930" d="M 1053,546 C 1053,169 920,-20 655,-20 573,-20 505,-5 451,25 396,54 352,102 318,168 L 316,168 C 316,147 315,116 312,74 309,31 307,7 306,0 L 132,0 C 136,36 138,110 138,223 L 138,1484 318,1484 318,1061 C 318,1018 317,967 314,908 L 318,908 C 351,977 396,1027 451,1057 506,1087 574,1102 655,1102 792,1102 892,1056 957,964 1021,872 1053,733 1053,546 Z M 864,540 C 864,691 844,800 804,865 764,930 699,963 609,963 508,963 434,928 388,859 341,790 318,680 318,529 318,387 341,282 386,215 431,147 505,113 607,113 698,113 763,147 804,214 844,281 864,389 864,540 Z"/>
|
||||
<glyph unicode="a" horiz-adv-x="1068" d="M 414,-20 C 305,-20 224,9 169,66 114,123 87,202 87,302 87,414 124,500 198,560 271,620 390,652 554,656 L 797,660 797,719 C 797,807 778,870 741,908 704,946 645,965 565,965 484,965 426,951 389,924 352,897 330,853 323,793 L 135,810 C 166,1005 310,1102 569,1102 705,1102 807,1071 876,1009 945,946 979,856 979,738 L 979,272 C 979,219 986,179 1000,152 1014,125 1041,111 1080,111 1097,111 1117,113 1139,118 L 1139,6 C 1094,-5 1047,-10 1000,-10 933,-10 885,8 855,43 824,78 807,132 803,207 L 797,207 C 751,124 698,66 637,32 576,-3 501,-20 414,-20 Z M 455,115 C 521,115 580,130 631,160 682,190 723,231 753,284 782,336 797,390 797,445 L 797,534 600,530 C 515,529 451,520 408,504 364,488 330,463 307,430 284,397 272,353 272,299 272,240 288,195 320,163 351,131 396,115 455,115 Z"/>
|
||||
<glyph unicode="^" horiz-adv-x="949" d="M 787,673 L 478,1306 172,673 10,673 378,1409 581,1409 951,673 787,673 Z"/>
|
||||
<glyph unicode="X" horiz-adv-x="1289" d="M 1112,0 L 689,616 257,0 46,0 582,732 87,1409 298,1409 690,856 1071,1409 1282,1409 800,739 1323,0 1112,0 Z"/>
|
||||
<glyph unicode="T" horiz-adv-x="1170" d="M 720,1253 L 720,0 530,0 530,1253 46,1253 46,1409 1204,1409 1204,1253 720,1253 Z"/>
|
||||
<glyph unicode="R" horiz-adv-x="1225" d="M 1164,0 L 798,585 359,585 359,0 168,0 168,1409 831,1409 C 990,1409 1112,1374 1199,1303 1285,1232 1328,1133 1328,1006 1328,901 1298,813 1237,742 1176,671 1091,626 984,607 L 1384,0 1164,0 Z M 1136,1004 C 1136,1086 1108,1149 1053,1192 997,1235 917,1256 812,1256 L 359,1256 359,736 820,736 C 921,736 999,760 1054,807 1109,854 1136,919 1136,1004 Z"/>
|
||||
<glyph unicode="P" horiz-adv-x="1096" d="M 1258,985 C 1258,852 1215,746 1128,667 1041,588 922,549 773,549 L 359,549 359,0 168,0 168,1409 761,1409 C 919,1409 1041,1372 1128,1298 1215,1224 1258,1120 1258,985 Z M 1066,983 C 1066,1165 957,1256 738,1256 L 359,1256 359,700 746,700 C 959,700 1066,794 1066,983 Z"/>
|
||||
<glyph unicode="O" horiz-adv-x="1409" d="M 1495,711 C 1495,564 1467,435 1411,324 1354,213 1273,128 1168,69 1063,10 938,-20 795,-20 650,-20 526,9 421,68 316,127 235,212 180,323 125,434 97,563 97,711 97,936 159,1113 282,1240 405,1367 577,1430 797,1430 940,1430 1065,1402 1170,1345 1275,1288 1356,1205 1412,1096 1467,987 1495,859 1495,711 Z M 1300,711 C 1300,886 1256,1024 1169,1124 1081,1224 957,1274 797,1274 636,1274 511,1225 423,1126 335,1027 291,889 291,711 291,534 336,394 425,291 514,187 637,135 795,135 958,135 1083,185 1170,286 1257,386 1300,528 1300,711 Z"/>
|
||||
<glyph unicode="A" horiz-adv-x="1372" d="M 1167,0 L 1006,412 364,412 202,0 4,0 579,1409 796,1409 1362,0 1167,0 Z M 685,1265 L 676,1237 C 659,1182 635,1111 602,1024 L 422,561 949,561 768,1026 C 749,1072 731,1124 712,1182 L 685,1265 Z"/>
|
||||
<glyph unicode="=" horiz-adv-x="1013" d="M 100,856 L 100,1004 1095,1004 1095,856 100,856 Z M 100,344 L 100,492 1095,492 1095,344 100,344 Z"/>
|
||||
<glyph unicode="8" horiz-adv-x="976" d="M 1050,393 C 1050,263 1009,162 926,89 843,16 725,-20 570,-20 419,-20 302,16 217,87 132,158 89,260 89,391 89,483 115,560 168,623 221,686 288,724 370,737 L 370,741 C 293,759 233,798 189,858 144,918 122,988 122,1069 122,1176 162,1263 243,1330 323,1397 431,1430 566,1430 705,1430 814,1397 895,1332 975,1267 1015,1178 1015,1067 1015,986 993,916 948,856 903,796 842,758 765,743 L 765,739 C 855,724 925,686 975,625 1025,563 1050,486 1050,393 Z M 828,1057 C 828,1216 741,1296 566,1296 481,1296 417,1276 373,1236 328,1196 306,1136 306,1057 306,976 329,915 375,873 420,830 485,809 568,809 653,809 717,829 762,868 806,907 828,970 828,1057 Z M 863,410 C 863,497 837,563 785,608 733,652 660,674 566,674 475,674 403,650 352,603 301,555 275,489 275,406 275,212 374,115 572,115 670,115 743,139 791,186 839,233 863,307 863,410 Z"/>
|
||||
<glyph unicode="7" horiz-adv-x="940" d="M 1036,1263 C 892,1043 790,871 731,746 672,621 627,498 598,377 568,256 553,130 553,0 L 365,0 C 365,180 403,370 480,569 556,768 683,997 862,1256 L 105,1256 105,1409 1036,1409 1036,1263 Z"/>
|
||||
<glyph unicode="6" horiz-adv-x="958" d="M 1049,461 C 1049,312 1009,195 928,109 847,23 736,-20 594,-20 435,-20 314,39 230,157 146,275 104,447 104,672 104,916 148,1103 235,1234 322,1365 447,1430 608,1430 821,1430 955,1334 1010,1143 L 838,1112 C 803,1227 725,1284 606,1284 503,1284 424,1236 368,1141 311,1045 283,906 283,725 316,786 362,832 421,864 480,895 548,911 625,911 755,911 858,870 935,789 1011,708 1049,598 1049,461 Z M 866,453 C 866,555 841,634 791,689 741,744 671,772 582,772 498,772 430,748 379,699 327,650 301,582 301,496 301,387 328,298 382,229 435,160 504,125 588,125 675,125 743,154 792,213 841,271 866,351 866,453 Z"/>
|
||||
<glyph unicode="5" horiz-adv-x="985" d="M 1053,459 C 1053,310 1009,193 921,108 832,23 710,-20 553,-20 422,-20 316,9 235,66 154,123 103,206 82,315 L 264,336 C 302,197 400,127 557,127 654,127 729,156 784,215 839,273 866,353 866,455 866,544 839,615 784,670 729,725 654,752 561,752 512,752 467,744 425,729 383,714 341,688 299,651 L 123,651 170,1409 971,1409 971,1256 334,1256 307,809 C 385,869 482,899 598,899 737,899 847,858 930,777 1012,696 1053,590 1053,459 Z"/>
|
||||
<glyph unicode="4" horiz-adv-x="1041" d="M 881,319 L 881,0 711,0 711,319 47,319 47,459 692,1409 881,1409 881,461 1079,461 1079,319 881,319 Z M 711,1206 C 710,1202 700,1184 683,1153 666,1122 653,1100 644,1087 L 283,555 229,481 213,461 711,461 711,1206 Z"/>
|
||||
<glyph unicode="3" horiz-adv-x="985" d="M 1049,389 C 1049,259 1008,158 925,87 842,16 724,-20 571,-20 428,-20 315,12 230,77 145,141 94,236 78,362 L 264,379 C 288,212 390,129 571,129 662,129 733,151 785,196 836,241 862,307 862,395 862,472 833,532 774,575 715,618 629,639 518,639 L 416,639 416,795 514,795 C 613,795 689,817 744,860 798,903 825,962 825,1038 825,1113 803,1173 759,1217 714,1260 648,1282 561,1282 482,1282 418,1262 369,1221 320,1180 291,1123 283,1049 L 102,1063 C 115,1178 163,1268 246,1333 328,1398 434,1430 563,1430 704,1430 814,1397 893,1332 971,1266 1010,1174 1010,1057 1010,967 985,894 935,838 884,781 811,743 715,723 L 715,719 C 820,708 902,672 961,613 1020,554 1049,479 1049,389 Z"/>
|
||||
<glyph unicode="2" horiz-adv-x="940" d="M 103,0 L 103,127 C 137,205 179,274 228,334 277,393 328,447 382,496 436,544 490,589 543,630 596,671 643,713 686,754 729,795 763,839 790,884 816,929 829,981 829,1038 829,1115 806,1175 761,1218 716,1261 653,1282 572,1282 495,1282 432,1261 383,1220 333,1178 304,1119 295,1044 L 111,1061 C 124,1174 172,1263 255,1330 337,1397 443,1430 572,1430 714,1430 823,1397 900,1330 976,1263 1014,1167 1014,1044 1014,989 1002,935 977,881 952,827 914,773 865,719 816,665 721,581 582,468 505,405 444,349 399,299 354,248 321,200 301,153 L 1036,153 1036,0 103,0 Z"/>
|
||||
<glyph unicode="1" horiz-adv-x="903" d="M 156,0 L 156,153 515,153 515,1237 197,1010 197,1180 530,1409 696,1409 696,153 1039,153 1039,0 156,0 Z"/>
|
||||
<glyph unicode="0" horiz-adv-x="995" d="M 1059,705 C 1059,470 1018,290 935,166 852,42 729,-20 567,-20 405,-20 283,42 202,165 121,288 80,468 80,705 80,947 120,1128 199,1249 278,1370 402,1430 573,1430 739,1430 862,1369 941,1247 1020,1125 1059,944 1059,705 Z M 876,705 C 876,908 853,1056 806,1147 759,1238 681,1284 573,1284 462,1284 383,1239 335,1149 286,1059 262,911 262,705 262,505 287,359 336,266 385,173 462,127 569,127 675,127 753,174 802,269 851,364 876,509 876,705 Z"/>
|
||||
<glyph unicode="." horiz-adv-x="204" d="M 187,0 L 187,219 382,219 382,0 187,0 Z"/>
|
||||
<glyph unicode="," horiz-adv-x="204" d="M 385,219 L 385,51 C 385,-20 379,-79 366,-126 353,-173 334,-219 307,-262 L 184,-262 C 247,-171 278,-84 278,0 L 190,0 190,219 385,219 Z"/>
|
||||
<glyph unicode=")" horiz-adv-x="553" d="M 555,528 C 555,335 525,162 465,9 404,-144 311,-289 186,-424 L 12,-424 C 137,-284 229,-136 287,19 345,174 374,344 374,530 374,716 345,887 287,1042 228,1197 137,1345 12,1484 L 186,1484 C 312,1348 405,1203 465,1050 525,896 555,723 555,532 L 555,528 Z"/>
|
||||
<glyph unicode="(" horiz-adv-x="553" d="M 127,532 C 127,725 157,898 218,1051 278,1204 371,1349 496,1484 L 670,1484 C 545,1345 454,1198 396,1042 337,886 308,715 308,530 308,345 337,175 395,20 452,-135 544,-283 670,-424 L 496,-424 C 370,-288 277,-143 217,11 157,164 127,337 127,528 L 127,532 Z"/>
|
||||
<glyph unicode=" " horiz-adv-x="571"/>
|
||||
</font>
|
||||
</defs>
|
||||
<defs>
|
||||
<font id="EmbeddedFont_2" horiz-adv-x="2048">
|
||||
<font-face font-family="Liberation Sans embedded" units-per-em="2048" font-weight="normal" font-style="italic" ascent="1859" descent="433"/>
|
||||
<missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
|
||||
<glyph unicode="y" horiz-adv-x="1253" d="M 16,-425 C -32,-425 -76,-420 -116,-411 L -85,-277 C -55,-282 -29,-285 -8,-285 55,-285 111,-264 160,-221 208,-178 255,-116 302,-35 L 329,12 112,1082 295,1082 407,484 C 417,431 426,374 435,314 444,253 448,214 449,196 456,211 464,229 475,250 486,271 637,549 928,1082 L 1127,1082 501,0 C 427,-129 368,-219 323,-271 278,-322 231,-361 182,-387 133,-412 77,-425 16,-425 Z"/>
|
||||
<glyph unicode="x" horiz-adv-x="1179" d="M 706,0 L 497,444 117,0 -82,0 410,556 146,1082 335,1082 528,661 878,1082 1084,1082 615,558 896,0 706,0 Z"/>
|
||||
</font>
|
||||
</defs>
|
||||
<defs>
|
||||
<font id="EmbeddedFont_3" horiz-adv-x="2048">
|
||||
<font-face font-family="Liberation Sans embedded" units-per-em="2048" font-weight="bold" font-style="normal" ascent="1859" descent="433"/>
|
||||
<missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
|
||||
<glyph unicode="t" horiz-adv-x="646" d="M 420,-18 C 337,-18 274,5 229,50 184,95 162,163 162,254 L 162,892 25,892 25,1082 176,1082 264,1336 440,1336 440,1082 645,1082 645,892 440,892 440,330 C 440,277 450,239 470,214 490,189 521,176 563,176 585,176 616,181 657,190 L 657,16 C 588,-7 509,-18 420,-18 Z"/>
|
||||
<glyph unicode="s" horiz-adv-x="995" d="M 1055,316 C 1055,211 1012,129 927,70 841,10 722,-20 571,-20 422,-20 309,4 230,51 151,98 98,171 72,270 L 319,307 C 333,256 357,219 392,198 426,177 486,166 571,166 650,166 707,176 743,196 779,216 797,247 797,290 797,325 783,352 754,373 725,393 675,410 606,424 447,455 340,485 285,512 230,539 188,574 159,617 130,660 115,712 115,775 115,878 155,959 235,1017 314,1074 427,1103 573,1103 702,1103 805,1078 884,1028 962,978 1011,906 1030,811 L 781,785 C 773,829 753,862 722,884 691,905 641,916 573,916 506,916 456,908 423,891 390,874 373,845 373,805 373,774 386,749 412,731 437,712 480,697 541,685 626,668 701,650 767,632 832,613 885,591 925,566 964,541 996,508 1020,469 1043,429 1055,378 1055,316 Z"/>
|
||||
<glyph unicode="r" horiz-adv-x="645" d="M 143,0 L 143,828 C 143,887 142,937 141,977 139,1016 137,1051 135,1082 L 403,1082 C 405,1070 408,1034 411,973 414,912 416,871 416,851 L 420,851 C 447,927 472,981 493,1012 514,1043 540,1066 569,1081 598,1096 635,1103 679,1103 715,1103 744,1098 766,1088 L 766,853 C 721,863 681,868 646,868 576,868 522,840 483,783 444,726 424,642 424,531 L 424,0 143,0 Z"/>
|
||||
<glyph unicode="o" horiz-adv-x="1105" d="M 1171,542 C 1171,367 1122,229 1025,130 928,30 793,-20 621,-20 452,-20 320,30 224,130 128,230 80,367 80,542 80,716 128,853 224,953 320,1052 454,1102 627,1102 804,1102 939,1054 1032,958 1125,861 1171,723 1171,542 Z M 877,542 C 877,671 856,764 814,822 772,880 711,909 631,909 460,909 375,787 375,542 375,421 396,330 438,267 479,204 539,172 618,172 791,172 877,295 877,542 Z"/>
|
||||
<glyph unicode="n" horiz-adv-x="1004" d="M 844,0 L 844,607 C 844,797 780,892 651,892 583,892 528,863 487,805 445,746 424,671 424,580 L 424,0 143,0 143,840 C 143,898 142,946 141,983 139,1020 137,1053 135,1082 L 403,1082 C 405,1069 408,1036 411,981 414,926 416,888 416,867 L 420,867 C 458,950 506,1010 563,1047 620,1084 689,1103 768,1103 883,1103 971,1068 1032,997 1093,926 1124,823 1124,687 L 1124,0 844,0 Z"/>
|
||||
<glyph unicode="l" horiz-adv-x="296" d="M 143,0 L 143,1484 424,1484 424,0 143,0 Z"/>
|
||||
<glyph unicode="i" horiz-adv-x="296" d="M 143,1277 L 143,1484 424,1484 424,1277 143,1277 Z M 143,0 L 143,1082 424,1082 424,0 143,0 Z"/>
|
||||
<glyph unicode="h" horiz-adv-x="995" d="M 420,866 C 458,949 506,1009 563,1046 620,1083 689,1102 768,1102 883,1102 971,1067 1032,996 1093,925 1124,822 1124,686 L 1124,0 844,0 844,606 C 844,796 780,891 651,891 583,891 528,862 487,804 445,745 424,670 424,579 L 424,0 143,0 143,1484 424,1484 424,1079 C 424,1006 421,935 416,866 L 420,866 Z"/>
|
||||
<glyph unicode="g" horiz-adv-x="1041" d="M 596,-434 C 464,-434 358,-409 278,-359 197,-308 148,-236 129,-143 L 410,-110 C 420,-153 442,-187 475,-212 508,-237 551,-249 604,-249 682,-249 739,-225 775,-177 811,-129 829,-58 829,37 L 829,94 831,201 829,201 C 767,68 651,2 481,2 355,2 257,49 188,144 119,239 84,374 84,550 84,727 120,863 191,959 262,1055 366,1103 502,1103 659,1103 768,1038 829,908 L 834,908 C 834,931 836,963 839,1003 842,1043 845,1069 848,1082 L 1114,1082 C 1110,1010 1108,927 1108,832 L 1108,33 C 1108,-121 1064,-237 977,-316 890,-395 763,-434 596,-434 Z M 831,556 C 831,667 811,754 772,817 732,879 675,910 602,910 452,910 377,790 377,550 377,315 451,197 600,197 675,197 732,228 772,291 811,353 831,441 831,556 Z"/>
|
||||
<glyph unicode="e" horiz-adv-x="1004" d="M 586,-20 C 423,-20 298,28 211,125 124,221 80,361 80,546 80,725 124,862 213,958 302,1054 427,1102 590,1102 745,1102 864,1051 946,948 1028,845 1069,694 1069,495 L 1069,487 375,487 C 375,382 395,302 434,249 473,195 528,168 600,168 699,168 762,211 788,297 L 1053,274 C 976,78 821,-20 586,-20 Z M 586,925 C 520,925 469,902 434,856 398,810 379,746 377,663 L 797,663 C 792,750 771,816 734,860 697,903 648,925 586,925 Z"/>
|
||||
<glyph unicode="c" horiz-adv-x="1013" d="M 594,-20 C 430,-20 303,29 214,127 125,224 80,360 80,535 80,714 125,853 215,953 305,1052 433,1102 598,1102 725,1102 831,1070 914,1006 997,942 1050,854 1071,741 L 788,727 C 780,782 760,827 728,860 696,893 651,909 592,909 447,909 375,788 375,546 375,297 449,172 596,172 649,172 694,189 730,223 766,256 788,306 797,373 L 1079,360 C 1069,286 1043,220 1000,162 957,104 900,59 830,28 760,-4 681,-20 594,-20 Z"/>
|
||||
<glyph unicode="2" horiz-adv-x="1005" d="M 71,0 L 71,195 C 108,276 160,354 228,431 295,508 380,588 483,671 582,751 651,817 691,869 730,921 750,972 750,1022 750,1145 688,1206 565,1206 505,1206 459,1190 428,1158 396,1125 375,1077 366,1012 L 83,1028 C 99,1159 148,1258 230,1327 311,1396 422,1430 563,1430 715,1430 832,1395 913,1326 994,1257 1035,1159 1035,1034 1035,968 1022,908 996,855 970,802 937,753 896,708 855,663 810,620 761,581 711,542 663,503 616,466 569,429 527,391 489,353 450,315 422,274 403,231 L 1057,231 1057,0 71,0 Z"/>
|
||||
</font>
|
||||
</defs>
|
||||
<defs class="TextShapeIndex">
|
||||
<g ooo:slide="id1" ooo:id-list="id3 id4 id5 id6 id7 id8 id9 id10 id11 id12 id13 id14 id15 id16 id17 id18 id19 id20 id21 id22 id23 id24 id25 id26 id27 id28 id29 id30 id31"/>
|
||||
</defs>
|
||||
<defs class="EmbeddedBulletChars">
|
||||
<g id="bullet-char-template-57356" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-57354" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-10146" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-10132" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-10007" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-10004" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-9679" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-8226" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-8211" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-61548" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"/>
|
||||
</g>
|
||||
</defs>
|
||||
<defs class="TextEmbeddedBitmaps"/>
|
||||
<g>
|
||||
<g id="id2" class="Master_Slide">
|
||||
<g id="bg-id2" class="Background"/>
|
||||
<g id="bo-id2" class="BackgroundObjects"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="SlideGroup">
|
||||
<g>
|
||||
<g id="container-id1">
|
||||
<g id="id1" class="Slide" clip-path="url(#presentation_clip_path)">
|
||||
<g class="Page">
|
||||
<g class="com.sun.star.drawing.TableShape">
|
||||
<g>
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="2348" y="1277" width="14104" height="1374"/>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 2350,1279 L 3231,1279 3231,2648 2350,2648 2350,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="2615" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 3231,1279 L 4112,1279 4112,2648 3231,2648 3231,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="3496" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 4112,1279 L 4993,1279 4993,2648 4112,2648 4112,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="4377" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 4993,1279 L 5874,1279 5874,2648 4993,2648 4993,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="5258" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 5874,1279 L 6755,1279 6755,2648 5874,2648 5874,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="6139" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 6755,1279 L 7636,1279 7636,2648 6755,2648 6755,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="7020" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 7636,1279 L 8517,1279 8517,2648 7636,2648 7636,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="7901" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 8517,1279 L 9398,1279 9398,2648 8517,2648 8517,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8782" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 9398,1279 L 10279,1279 10279,2648 9398,2648 9398,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="9663" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 10279,1279 L 11160,1279 11160,2648 10279,2648 10279,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="10544" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 11160,1279 L 12041,1279 12041,2648 11160,2648 11160,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="11425" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 12041,1279 L 12922,1279 12922,2648 12041,2648 12041,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="12306" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 12922,1279 L 13803,1279 13803,2648 12922,2648 12922,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="13187" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 13803,1279 L 14684,1279 14684,2648 13803,2648 13803,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="14068" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 14684,1279 L 15565,1279 15565,2648 14684,2648 14684,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="14949" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 15565,1279 L 16449,1279 16449,2648 15565,2648 15565,1279 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="15832" y="2184"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2349,1279 L 3232,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2349,2648 L 3232,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3230,1279 L 4113,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3230,2648 L 4113,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4111,1279 L 4994,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4111,2648 L 4994,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4992,1279 L 5875,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4992,2648 L 5875,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5873,1279 L 6756,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5873,2648 L 6756,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6754,1279 L 7637,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6754,2648 L 7637,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7635,1279 L 8518,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7635,2648 L 8518,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8516,1279 L 9399,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8516,2648 L 9399,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9397,1279 L 10280,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9397,2648 L 10280,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10278,1279 L 11161,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10278,2648 L 11161,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11159,1279 L 12042,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11159,2648 L 12042,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12040,1279 L 12923,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12040,2648 L 12923,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12921,1279 L 13804,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12921,2648 L 13804,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13802,1279 L 14685,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13802,2648 L 14685,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14683,1279 L 15566,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14683,2648 L 15566,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15564,1279 L 16450,1279"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15564,2648 L 16450,2648"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2350,1278 L 2350,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3231,1278 L 3231,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4112,1278 L 4112,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4993,1278 L 4993,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5874,1278 L 5874,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6755,1278 L 6755,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7636,1278 L 7636,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8517,1278 L 8517,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9398,1278 L 9398,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10279,1278 L 10279,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11160,1278 L 11160,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12041,1278 L 12041,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12922,1278 L 12922,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13803,1278 L 13803,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14684,1278 L 14684,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15565,1278 L 15565,2649"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 16449,1278 L 16449,2649"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TableShape">
|
||||
<g>
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="2349" y="3278" width="14108" height="1378"/>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 2351,3280 L 3232,3280 3232,4653 2351,4653 2351,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="2616" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 3232,3280 L 4113,3280 4113,4653 3232,4653 3232,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="3497" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 4113,3280 L 4994,3280 4994,4653 4113,4653 4113,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="4378" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 4994,3280 L 5875,3280 5875,4653 4994,4653 4994,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="5259" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 5875,3280 L 6756,3280 6756,4653 5875,4653 5875,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="6140" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 6756,3280 L 7637,3280 7637,4653 6756,4653 6756,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="7021" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 7637,3280 L 8518,3280 8518,4653 7637,4653 7637,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="7902" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 8518,3280 L 9399,3280 9399,4653 8518,4653 8518,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8783" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 9399,3280 L 10280,3280 10280,4653 9399,4653 9399,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="9664" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 10280,3280 L 11161,3280 11161,4653 10280,4653 10280,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="10545" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 11161,3280 L 12042,3280 12042,4653 11161,4653 11161,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="11426" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 12042,3280 L 12923,3280 12923,4653 12042,4653 12042,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="12307" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 12923,3280 L 13804,3280 13804,4653 12923,4653 12923,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="13188" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 13804,3280 L 14685,3280 14685,4653 13804,4653 13804,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="14069" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 14685,3280 L 15566,3280 15566,4653 14685,4653 14685,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="14950" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 15566,3280 L 16454,3280 16454,4653 15566,4653 15566,3280 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="15835" y="4187"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2350,3280 L 3233,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2350,4653 L 3233,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3231,3280 L 4114,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3231,4653 L 4114,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4112,3280 L 4995,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4112,4653 L 4995,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4993,3280 L 5876,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4993,4653 L 5876,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5874,3280 L 6757,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5874,4653 L 6757,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6755,3280 L 7638,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6755,4653 L 7638,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7636,3280 L 8519,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7636,4653 L 8519,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8517,3280 L 9400,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8517,4653 L 9400,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9398,3280 L 10281,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9398,4653 L 10281,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10279,3280 L 11162,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10279,4653 L 11162,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11160,3280 L 12043,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11160,4653 L 12043,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12041,3280 L 12924,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12041,4653 L 12924,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12922,3280 L 13805,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12922,4653 L 13805,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13803,3280 L 14686,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13803,4653 L 14686,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14684,3280 L 15567,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14684,4653 L 15567,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15565,3280 L 16455,3280"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15565,4653 L 16455,4653"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2351,3279 L 2351,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3232,3279 L 3232,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4113,3279 L 4113,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4994,3279 L 4994,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5875,3279 L 5875,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6756,3279 L 6756,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7637,3279 L 7637,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8518,3279 L 8518,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9399,3279 L 9399,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10280,3279 L 10280,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11161,3279 L 11161,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12042,3279 L 12042,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12923,3279 L 12923,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13804,3279 L 13804,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14685,3279 L 14685,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15566,3279 L 15566,4654"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 16454,3279 L 16454,4654"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TableShape">
|
||||
<g>
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="2255" y="7057" width="14108" height="1378"/>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 2257,7059 L 3138,7059 3138,8432 2257,8432 2257,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="2522" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 3138,7059 L 4019,7059 4019,8432 3138,8432 3138,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="3403" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 4019,7059 L 4900,7059 4900,8432 4019,8432 4019,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="4284" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 4900,7059 L 5781,7059 5781,8432 4900,8432 4900,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="5165" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 5781,7059 L 6662,7059 6662,8432 5781,8432 5781,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="6046" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 6662,7059 L 7543,7059 7543,8432 6662,8432 6662,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="6927" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 7543,7059 L 8424,7059 8424,8432 7543,8432 7543,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="7808" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 8424,7059 L 9305,7059 9305,8432 8424,8432 8424,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8689" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 9305,7059 L 10186,7059 10186,8432 9305,8432 9305,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="9570" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 10186,7059 L 11067,7059 11067,8432 10186,8432 10186,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="10451" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 11067,7059 L 11948,7059 11948,8432 11067,8432 11067,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="11332" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 11948,7059 L 12829,7059 12829,8432 11948,8432 11948,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="12213" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 12829,7059 L 13710,7059 13710,8432 12829,8432 12829,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="13094" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 13710,7059 L 14591,7059 14591,8432 13710,8432 13710,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="13975" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 14591,7059 L 15472,7059 15472,8432 14591,8432 14591,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="14856" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 15472,7059 L 16360,7059 16360,8432 15472,8432 15472,7059 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="15741" y="7966"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2256,7059 L 3139,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2256,8432 L 3139,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3137,7059 L 4020,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3137,8432 L 4020,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4018,7059 L 4901,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4018,8432 L 4901,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4899,7059 L 5782,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4899,8432 L 5782,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5780,7059 L 6663,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5780,8432 L 6663,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6661,7059 L 7544,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6661,8432 L 7544,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7542,7059 L 8425,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7542,8432 L 8425,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8423,7059 L 9306,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8423,8432 L 9306,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9304,7059 L 10187,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9304,8432 L 10187,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10185,7059 L 11068,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10185,8432 L 11068,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11066,7059 L 11949,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11066,8432 L 11949,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11947,7059 L 12830,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11947,8432 L 12830,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12828,7059 L 13711,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12828,8432 L 13711,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13709,7059 L 14592,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13709,8432 L 14592,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14590,7059 L 15473,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14590,8432 L 15473,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15471,7059 L 16361,7059"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15471,8432 L 16361,8432"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2257,7058 L 2257,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3138,7058 L 3138,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4019,7058 L 4019,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4900,7058 L 4900,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5781,7058 L 5781,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6662,7058 L 6662,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7543,7058 L 7543,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8424,7058 L 8424,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9305,7058 L 9305,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10186,7058 L 10186,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11067,7058 L 11067,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11948,7058 L 11948,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12829,7058 L 12829,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13710,7058 L 13710,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14591,7058 L 14591,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15472,7058 L 15472,8433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 16360,7058 L 16360,8433"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id3">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="16937" y="1740" width="3322" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="17187" y="2441"><tspan fill="rgb(0,0,0)" stroke="none">Address x</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id4">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="16938" y="3540" width="3322" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="17188" y="4241"><tspan fill="rgb(0,0,0)" stroke="none">Address y</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id5">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="8622" y="5060" width="1401" height="1782"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 8972,5061 L 8972,6395 8623,6395 9322,6840 10021,6395 9671,6395 9671,5061 8972,5061 Z M 8623,5061 L 8623,5061 Z M 10021,6840 L 10021,6840 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8972,5061 L 8972,6395 8623,6395 9322,6840 10021,6395 9671,6395 9671,5061 8972,5061 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8623,5061 L 8623,5061 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10021,6840 L 10021,6840 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id6">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="17133" y="7523" width="2693" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="17383" y="8224"><tspan font-style="italic" fill="rgb(0,0,0)" stroke="none">x</tspan><tspan fill="rgb(0,0,0)" stroke="none">XOR</tspan><tspan font-style="italic" fill="rgb(0,0,0)" stroke="none">y</tspan><tspan fill="rgb(0,0,0)" stroke="none"> </tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id7">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9854" y="5342" width="2057" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="10104" y="6043"><tspan fill="rgb(0,0,0)" stroke="none">XOR </tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id8">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="8622" y="9061" width="1401" height="1782"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 8972,9062 L 8972,10396 8623,10396 9322,10841 10021,10396 9671,10396 9671,9062 8972,9062 Z M 8623,9062 L 8623,9062 Z M 10021,10841 L 10021,10841 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8972,9062 L 8972,10396 8623,10396 9322,10841 10021,10396 9671,10396 9671,9062 8972,9062 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8623,9062 L 8623,9062 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10021,10841 L 10021,10841 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id9">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9854" y="9343" width="2404" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="10104" y="10044"><tspan fill="rgb(0,0,0)" stroke="none">binary </tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id10">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="8181" y="11192" width="2260" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8431" y="11893"><tspan fill="rgb(0,0,0)" stroke="none">14524</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id11">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9275" y="11993" width="503" height="1188"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id12">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9706" y="12111" width="503" height="1188"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id13">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11340" y="22079" width="5663" height="1674"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="700"><tspan class="TextPosition" x="11590" y="22780"><tspan fill="rgb(0,0,0)" stroke="none">int</tspan><tspan font-weight="400" fill="rgb(0,0,0)" stroke="none">(Proximity(x,y))</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id14">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11116" y="13758" width="503" height="1188"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id15">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11488" y="14128" width="503" height="1188"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id16">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11488" y="14128" width="503" height="1188"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id17">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11488" y="14128" width="503" height="1188"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id18">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11488" y="14128" width="503" height="1188"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id19">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="8622" y="12862" width="1401" height="1782"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 8972,12863 L 8972,14197 8623,14197 9322,14642 10021,14197 9671,14197 9671,12863 8972,12863 Z M 8623,12863 L 8623,12863 Z M 10021,14642 L 10021,14642 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8972,12863 L 8972,14197 8623,14197 9322,14642 10021,14197 9671,14197 9671,12863 8972,12863 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8623,12863 L 8623,12863 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10021,14642 L 10021,14642 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id20">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9853" y="12992" width="4329" height="1115"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="10103" y="13694"><tspan fill="rgb(0,0,0)" stroke="none">log</tspan></tspan><tspan class="TextPosition" y="13904"><tspan font-size="368px" fill="rgb(0,0,0)" stroke="none">2</tspan></tspan><tspan class="TextPosition" y="13694"><tspan fill="rgb(0,0,0)" stroke="none">(distance)</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id21">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="10753" y="10361" width="9847" height="1746"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="11003" y="11063"><tspan fill="rgb(0,0,0)" stroke="none"> </tspan><tspan fill="rgb(0,0,0)" stroke="none">distance (maxdistance = 16)</tspan></tspan><tspan class="TextPosition" x="11003" y="11774"><tspan fill="rgb(0,0,0)" stroke="none"> </tspan><tspan font-size="494px" fill="rgb(0,0,0)" stroke="none"> </tspan><tspan font-size="459px" fill="rgb(0,0,0)" stroke="none">= </tspan><tspan font-size="459px" fill="rgb(0,0,0)" stroke="none">log</tspan></tspan><tspan class="TextPosition" y="11925"><tspan font-size="266px" fill="rgb(0,0,0)" stroke="none">2</tspan></tspan><tspan class="TextPosition" y="11774"><tspan font-size="459px" fill="rgb(0,0,0)" stroke="none">(2^16)</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id22">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="8104" y="18650" width="2414" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8354" y="19351"><tspan fill="rgb(0,0,0)" stroke="none">2.174</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id23">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="8622" y="16463" width="1401" height="1782"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 8972,16464 L 8972,17798 8623,17798 9322,18243 10021,17798 9671,17798 9671,16464 8972,16464 Z M 8623,16464 L 8623,16464 Z M 10021,18243 L 10021,18243 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8972,16464 L 8972,17798 8623,17798 9322,18243 10021,17798 9671,17798 9671,16464 8972,16464 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8623,16464 L 8623,16464 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10021,18243 L 10021,18243 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id24">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9852" y="16282" width="10061" height="1826"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="10102" y="16984"><tspan fill="rgb(0,0,0)" stroke="none">log</tspan></tspan><tspan class="TextPosition" y="17194"><tspan font-size="368px" fill="rgb(0,0,0)" stroke="none">2</tspan></tspan><tspan class="TextPosition" y="16984"><tspan fill="rgb(0,0,0)" stroke="none">(maxdistance) – log</tspan></tspan><tspan class="TextPosition" y="17194"><tspan font-size="368px" fill="rgb(0,0,0)" stroke="none">2</tspan></tspan><tspan class="TextPosition" y="16984"><tspan fill="rgb(0,0,0)" stroke="none">(distance)</tspan></tspan><tspan class="TextPosition" x="10102" y="17847"><tspan fill="rgb(0,0,0)" stroke="none"> </tspan><tspan font-size="529px" fill="rgb(0,0,0)" stroke="none"> </tspan><tspan font-size="494px" fill="rgb(0,0,0)" stroke="none">16</tspan><tspan font-size="494px" fill="rgb(0,0,0)" stroke="none"> – 13.826</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id25">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="8125" y="14713" width="2971" height="964"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8375" y="15414"><tspan fill="rgb(0,0,0)" stroke="none">13.826 </tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id26">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="8622" y="20046" width="1401" height="1782"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 8972,20047 L 8972,21381 8623,21381 9322,21826 10021,21381 9671,21381 9671,20047 8972,20047 Z M 8623,20047 L 8623,20047 Z M 10021,21826 L 10021,21826 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8972,20047 L 8972,21381 8623,21381 9322,21826 10021,21381 9671,21381 9671,20047 8972,20047 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8623,20047 L 8623,20047 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10021,21826 L 10021,21826 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id27">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="8866" y="22133" width="1017" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="700"><tspan class="TextPosition" x="9116" y="22834"><tspan fill="rgb(0,0,0)" stroke="none">2</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id28">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="2224" y="8661" width="1694" height="846"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="81" stroke-linejoin="round" d="M 2265,8702 C 2265,8893 2332,9084 2399,9084 L 2936,9084 C 3003,9084 3070,9275 3070,9465 3070,9275 3137,9084 3204,9084 L 3741,9084 C 3808,9084 3876,8893 3876,8702"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="81" stroke-linejoin="round" d="M 2265,9465 L 2265,9465 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" stroke-width="81" stroke-linejoin="round" d="M 3876,8702 L 3876,8702 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id29">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="3112" y="9878" width="5756" height="12738"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 3126,9879 L 3113,9879 3113,22614 8866,22614"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id30">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="3539" y="24113" width="13721" height="1676"/>
|
||||
<path fill="rgb(255,229,202)" stroke="none" d="M 10399,25787 L 3540,25787 3540,24114 17258,24114 17258,25787 10399,25787 Z"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 10399,25787 L 3540,25787 3540,24114 17258,24114 17258,25787 10399,25787 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="5672" y="24815"><tspan fill="rgb(206,24,30)" stroke="none">The </tspan><tspan font-weight="700" fill="rgb(206,24,30)" stroke="none">higher</tspan><tspan fill="rgb(206,24,30)" stroke="none"> the proximity number,</tspan></tspan><tspan class="TextPosition" x="3790" y="25526"><tspan fill="rgb(206,24,30)" stroke="none">the </tspan><tspan font-weight="700" fill="rgb(206,24,30)" stroke="none">closer</tspan><tspan fill="rgb(206,24,30)" stroke="none"> to each other the two addresses are</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id31">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11340" y="18679" width="4347" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="11590" y="19380"><tspan fill="rgb(0,0,0)" stroke="none">Proximity(x,y)</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 78 KiB |
698
docs/swarm-guide/contents/img/dpa-chunking.svg
Normal file
@ -0,0 +1,698 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.2" width="210mm" height="297mm" viewBox="0 0 21000 29700" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
|
||||
<defs class="ClipPathGroup">
|
||||
<clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="21000" height="29700"/>
|
||||
</clipPath>
|
||||
<clipPath id="presentation_clip_path_shrink" clipPathUnits="userSpaceOnUse">
|
||||
<rect x="21" y="29" width="20958" height="29641"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<defs>
|
||||
<font id="EmbeddedFont_1" horiz-adv-x="2048">
|
||||
<font-face font-family="Liberation Sans embedded" units-per-em="2048" font-weight="normal" font-style="normal" ascent="1859" descent="433"/>
|
||||
<missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
|
||||
<glyph unicode="y" horiz-adv-x="1032" d="M 191,-425 C 142,-425 100,-421 67,-414 L 67,-279 C 92,-283 120,-285 151,-285 263,-285 352,-203 417,-38 L 434,5 5,1082 197,1082 425,484 C 428,475 432,464 437,451 442,438 457,394 482,320 507,246 521,205 523,196 L 593,393 830,1082 1020,1082 604,0 C 559,-115 518,-201 479,-258 440,-314 398,-356 351,-384 304,-411 250,-425 191,-425 Z"/>
|
||||
<glyph unicode="w" horiz-adv-x="1501" d="M 1174,0 L 965,0 776,765 740,934 C 734,904 725,861 712,805 699,748 631,480 508,0 L 300,0 -3,1082 175,1082 358,347 C 363,331 377,265 401,149 L 418,223 644,1082 837,1082 1026,339 1072,149 1103,288 1308,1082 1484,1082 1174,0 Z"/>
|
||||
<glyph unicode="v" horiz-adv-x="1023" d="M 613,0 L 400,0 7,1082 199,1082 437,378 C 446,351 469,272 506,141 L 541,258 580,376 826,1082 1017,1082 613,0 Z"/>
|
||||
<glyph unicode="u" horiz-adv-x="884" d="M 314,1082 L 314,396 C 314,325 321,269 335,230 349,191 371,162 402,145 433,128 478,119 537,119 624,119 692,149 742,208 792,267 817,350 817,455 L 817,1082 997,1082 997,231 C 997,105 999,28 1003,0 L 833,0 C 832,3 832,12 831,27 830,42 830,59 829,78 828,97 826,132 825,185 L 822,185 C 781,110 733,58 679,27 624,-4 557,-20 476,-20 357,-20 271,10 216,69 161,128 133,225 133,361 L 133,1082 314,1082 Z"/>
|
||||
<glyph unicode="t" horiz-adv-x="534" d="M 554,8 C 495,-8 434,-16 372,-16 228,-16 156,66 156,229 L 156,951 31,951 31,1082 163,1082 216,1324 336,1324 336,1082 536,1082 536,951 336,951 336,268 C 336,216 345,180 362,159 379,138 408,127 450,127 474,127 509,132 554,141 L 554,8 Z"/>
|
||||
<glyph unicode="s" horiz-adv-x="903" d="M 950,299 C 950,197 912,118 835,63 758,8 650,-20 511,-20 376,-20 273,2 200,47 127,91 79,160 57,254 L 216,285 C 231,227 263,185 311,158 359,131 426,117 511,117 602,117 669,131 712,159 754,187 775,229 775,285 775,328 760,362 731,389 702,416 654,438 589,455 L 460,489 C 357,516 283,542 240,568 196,593 162,624 137,661 112,698 100,743 100,796 100,895 135,970 206,1022 276,1073 378,1099 513,1099 632,1099 727,1078 798,1036 868,994 912,927 931,834 L 769,814 C 759,862 732,899 689,925 645,950 586,963 513,963 432,963 372,951 333,926 294,901 275,864 275,814 275,783 283,758 299,738 315,718 339,701 370,687 401,673 467,654 568,629 663,605 732,583 774,563 816,542 849,520 874,495 898,470 917,442 930,410 943,377 950,340 950,299 Z"/>
|
||||
<glyph unicode="r" horiz-adv-x="525" d="M 142,0 L 142,830 C 142,906 140,990 136,1082 L 306,1082 C 311,959 314,886 314,861 L 318,861 C 347,954 380,1017 417,1051 454,1085 507,1102 575,1102 599,1102 623,1099 648,1092 L 648,927 C 624,934 592,937 552,937 477,937 420,905 381,841 342,776 322,684 322,564 L 322,0 142,0 Z"/>
|
||||
<glyph unicode="p" horiz-adv-x="930" d="M 1053,546 C 1053,169 920,-20 655,-20 488,-20 376,43 319,168 L 314,168 C 317,163 318,106 318,-2 L 318,-425 138,-425 138,861 C 138,972 136,1046 132,1082 L 306,1082 C 307,1079 308,1070 309,1054 310,1037 312,1012 314,978 315,944 316,921 316,908 L 320,908 C 352,975 394,1024 447,1055 500,1086 569,1101 655,1101 788,1101 888,1056 954,967 1020,878 1053,737 1053,546 Z M 864,542 C 864,693 844,800 803,865 762,930 698,962 609,962 538,962 482,947 442,917 401,887 371,840 350,777 329,713 318,630 318,528 318,386 341,281 386,214 431,147 505,113 607,113 696,113 762,146 803,212 844,277 864,387 864,542 Z"/>
|
||||
<glyph unicode="o" horiz-adv-x="976" d="M 1053,542 C 1053,353 1011,212 928,119 845,26 724,-20 565,-20 407,-20 288,28 207,125 126,221 86,360 86,542 86,915 248,1102 571,1102 736,1102 858,1057 936,966 1014,875 1053,733 1053,542 Z M 864,542 C 864,691 842,800 798,868 753,935 679,969 574,969 469,969 393,935 346,866 299,797 275,689 275,542 275,399 298,292 345,221 391,149 464,113 563,113 671,113 748,148 795,217 841,286 864,395 864,542 Z"/>
|
||||
<glyph unicode="n" horiz-adv-x="884" d="M 825,0 L 825,686 C 825,757 818,813 804,852 790,891 768,920 737,937 706,954 661,963 602,963 515,963 447,933 397,874 347,815 322,732 322,627 L 322,0 142,0 142,851 C 142,977 140,1054 136,1082 L 306,1082 C 307,1079 307,1070 308,1055 309,1040 310,1024 311,1005 312,986 313,950 314,897 L 317,897 C 358,972 406,1025 461,1056 515,1087 582,1102 663,1102 782,1102 869,1073 924,1014 979,955 1006,857 1006,721 L 1006,0 825,0 Z"/>
|
||||
<glyph unicode="m" horiz-adv-x="1455" d="M 768,0 L 768,686 C 768,791 754,863 725,903 696,943 645,963 570,963 493,963 433,934 388,875 343,816 321,734 321,627 L 321,0 142,0 142,851 C 142,977 140,1054 136,1082 L 306,1082 C 307,1079 307,1070 308,1055 309,1040 310,1024 311,1005 312,986 313,950 314,897 L 317,897 C 356,974 400,1027 450,1057 500,1087 561,1102 633,1102 715,1102 780,1086 828,1053 875,1020 908,968 927,897 L 930,897 C 967,970 1013,1022 1066,1054 1119,1086 1183,1102 1258,1102 1367,1102 1447,1072 1497,1013 1546,954 1571,856 1571,721 L 1571,0 1393,0 1393,686 C 1393,791 1379,863 1350,903 1321,943 1270,963 1195,963 1116,963 1055,934 1012,876 968,817 946,734 946,627 L 946,0 768,0 Z"/>
|
||||
<glyph unicode="l" horiz-adv-x="185" d="M 138,0 L 138,1484 318,1484 318,0 138,0 Z"/>
|
||||
<glyph unicode="k" horiz-adv-x="894" d="M 816,0 L 450,494 318,385 318,0 138,0 138,1484 318,1484 318,557 793,1082 1004,1082 565,617 1027,0 816,0 Z"/>
|
||||
<glyph unicode="i" horiz-adv-x="194" d="M 137,1312 L 137,1484 317,1484 317,1312 137,1312 Z M 137,0 L 137,1082 317,1082 317,0 137,0 Z"/>
|
||||
<glyph unicode="h" horiz-adv-x="875" d="M 317,897 C 356,968 402,1020 457,1053 511,1086 580,1102 663,1102 780,1102 867,1073 923,1015 978,956 1006,858 1006,721 L 1006,0 825,0 825,686 C 825,762 818,819 804,856 790,893 767,920 735,937 703,954 659,963 602,963 517,963 450,934 399,875 348,816 322,737 322,638 L 322,0 142,0 142,1484 322,1484 322,1098 C 322,1057 321,1015 319,972 316,929 315,904 314,897 L 317,897 Z"/>
|
||||
<glyph unicode="g" horiz-adv-x="930" d="M 548,-425 C 430,-425 336,-402 266,-356 196,-309 151,-243 131,-158 L 312,-132 C 324,-182 351,-220 392,-248 433,-274 486,-288 553,-288 732,-288 822,-183 822,27 L 822,201 820,201 C 786,132 739,80 680,45 621,10 551,-8 472,-8 339,-8 242,36 180,124 117,212 86,350 86,539 86,730 120,872 187,963 254,1054 355,1099 492,1099 569,1099 635,1082 692,1047 748,1012 791,962 822,897 L 824,897 C 824,917 825,952 828,1001 831,1050 833,1077 836,1082 L 1007,1082 C 1003,1046 1001,971 1001,858 L 1001,31 C 1001,-273 850,-425 548,-425 Z M 822,541 C 822,629 810,705 786,769 762,832 728,881 685,915 641,948 591,965 536,965 444,965 377,932 335,865 293,798 272,690 272,541 272,393 292,287 331,222 370,157 438,125 533,125 590,125 640,142 684,175 728,208 762,256 786,319 810,381 822,455 822,541 Z"/>
|
||||
<glyph unicode="f" horiz-adv-x="553" d="M 361,951 L 361,0 181,0 181,951 29,951 29,1082 181,1082 181,1204 C 181,1303 203,1374 246,1417 289,1460 356,1482 445,1482 495,1482 537,1478 572,1470 L 572,1333 C 542,1338 515,1341 492,1341 446,1341 413,1329 392,1306 371,1283 361,1240 361,1179 L 361,1082 572,1082 572,951 361,951 Z"/>
|
||||
<glyph unicode="e" horiz-adv-x="976" d="M 276,503 C 276,379 302,283 353,216 404,149 479,115 578,115 656,115 719,131 766,162 813,193 844,233 861,281 L 1019,236 C 954,65 807,-20 578,-20 418,-20 296,28 213,123 129,218 87,360 87,548 87,727 129,864 213,959 296,1054 416,1102 571,1102 889,1102 1048,910 1048,527 L 1048,503 276,503 Z M 862,641 C 852,755 823,838 775,891 727,943 658,969 568,969 481,969 412,940 361,882 310,823 282,743 278,641 L 862,641 Z"/>
|
||||
<glyph unicode="d" horiz-adv-x="930" d="M 821,174 C 788,105 744,55 689,25 634,-5 565,-20 484,-20 347,-20 247,26 183,118 118,210 86,349 86,536 86,913 219,1102 484,1102 566,1102 634,1087 689,1057 744,1027 788,979 821,914 L 823,914 821,1035 821,1484 1001,1484 1001,223 C 1001,110 1003,36 1007,0 L 835,0 C 833,11 831,35 829,74 826,113 825,146 825,174 L 821,174 Z M 275,542 C 275,391 295,282 335,217 375,152 440,119 530,119 632,119 706,154 752,225 798,296 821,405 821,554 821,697 798,802 752,869 706,936 633,969 532,969 441,969 376,936 336,869 295,802 275,693 275,542 Z"/>
|
||||
<glyph unicode="c" horiz-adv-x="894" d="M 275,546 C 275,402 298,295 343,226 388,157 457,122 548,122 612,122 666,139 709,174 752,209 778,262 788,334 L 970,322 C 956,218 912,135 837,73 762,11 668,-20 553,-20 402,-20 286,28 207,124 127,219 87,359 87,542 87,724 127,863 207,959 287,1054 402,1102 551,1102 662,1102 754,1073 827,1016 900,959 945,880 964,779 L 779,765 C 770,825 746,873 708,908 670,943 616,961 546,961 451,961 382,929 339,866 296,803 275,696 275,546 Z"/>
|
||||
<glyph unicode="b" horiz-adv-x="930" d="M 1053,546 C 1053,169 920,-20 655,-20 573,-20 505,-5 451,25 396,54 352,102 318,168 L 316,168 C 316,147 315,116 312,74 309,31 307,7 306,0 L 132,0 C 136,36 138,110 138,223 L 138,1484 318,1484 318,1061 C 318,1018 317,967 314,908 L 318,908 C 351,977 396,1027 451,1057 506,1087 574,1102 655,1102 792,1102 892,1056 957,964 1021,872 1053,733 1053,546 Z M 864,540 C 864,691 844,800 804,865 764,930 699,963 609,963 508,963 434,928 388,859 341,790 318,680 318,529 318,387 341,282 386,215 431,147 505,113 607,113 698,113 763,147 804,214 844,281 864,389 864,540 Z"/>
|
||||
<glyph unicode="a" horiz-adv-x="1068" d="M 414,-20 C 305,-20 224,9 169,66 114,123 87,202 87,302 87,414 124,500 198,560 271,620 390,652 554,656 L 797,660 797,719 C 797,807 778,870 741,908 704,946 645,965 565,965 484,965 426,951 389,924 352,897 330,853 323,793 L 135,810 C 166,1005 310,1102 569,1102 705,1102 807,1071 876,1009 945,946 979,856 979,738 L 979,272 C 979,219 986,179 1000,152 1014,125 1041,111 1080,111 1097,111 1117,113 1139,118 L 1139,6 C 1094,-5 1047,-10 1000,-10 933,-10 885,8 855,43 824,78 807,132 803,207 L 797,207 C 751,124 698,66 637,32 576,-3 501,-20 414,-20 Z M 455,115 C 521,115 580,130 631,160 682,190 723,231 753,284 782,336 797,390 797,445 L 797,534 600,530 C 515,529 451,520 408,504 364,488 330,463 307,430 284,397 272,353 272,299 272,240 288,195 320,163 351,131 396,115 455,115 Z"/>
|
||||
<glyph unicode="S" horiz-adv-x="1188" d="M 1272,389 C 1272,259 1221,158 1120,87 1018,16 875,-20 690,-20 347,-20 148,99 93,338 L 278,375 C 299,290 345,228 414,189 483,149 578,129 697,129 820,129 916,150 983,193 1050,235 1083,297 1083,379 1083,425 1073,462 1052,491 1031,520 1001,543 963,562 925,581 880,596 827,609 774,622 716,635 652,650 541,675 456,699 399,724 341,749 295,776 262,807 229,837 203,872 186,913 168,954 159,1000 159,1053 159,1174 205,1267 298,1332 390,1397 522,1430 694,1430 854,1430 976,1406 1061,1357 1146,1308 1205,1224 1239,1106 L 1051,1073 C 1030,1148 991,1202 933,1236 875,1269 795,1286 692,1286 579,1286 493,1267 434,1230 375,1193 345,1137 345,1063 345,1020 357,984 380,956 403,927 436,903 479,884 522,864 609,840 738,811 781,801 825,791 868,781 911,770 952,758 991,744 1030,729 1067,712 1102,693 1136,674 1166,650 1191,622 1216,594 1236,561 1251,523 1265,485 1272,440 1272,389 Z"/>
|
||||
<glyph unicode="P" horiz-adv-x="1096" d="M 1258,985 C 1258,852 1215,746 1128,667 1041,588 922,549 773,549 L 359,549 359,0 168,0 168,1409 761,1409 C 919,1409 1041,1372 1128,1298 1215,1224 1258,1120 1258,985 Z M 1066,983 C 1066,1165 957,1256 738,1256 L 359,1256 359,700 746,700 C 959,700 1066,794 1066,983 Z"/>
|
||||
<glyph unicode="N" horiz-adv-x="1151" d="M 1082,0 L 328,1200 333,1103 338,936 338,0 168,0 168,1409 390,1409 1152,201 C 1144,332 1140,426 1140,485 L 1140,1409 1312,1409 1312,0 1082,0 Z"/>
|
||||
<glyph unicode="L" horiz-adv-x="912" d="M 168,0 L 168,1409 359,1409 359,156 1071,156 1071,0 168,0 Z"/>
|
||||
<glyph unicode="D" horiz-adv-x="1225" d="M 1381,719 C 1381,574 1353,447 1296,338 1239,229 1159,145 1055,87 951,29 831,0 695,0 L 168,0 168,1409 634,1409 C 873,1409 1057,1349 1187,1230 1316,1110 1381,940 1381,719 Z M 1189,719 C 1189,894 1141,1027 1046,1119 950,1210 811,1256 630,1256 L 359,1256 359,153 673,153 C 776,153 867,176 946,221 1024,266 1084,332 1126,417 1168,502 1189,603 1189,719 Z"/>
|
||||
<glyph unicode="C" horiz-adv-x="1308" d="M 792,1274 C 636,1274 515,1224 428,1124 341,1023 298,886 298,711 298,538 343,400 434,295 524,190 646,137 800,137 997,137 1146,235 1245,430 L 1401,352 C 1343,231 1262,138 1157,75 1052,12 930,-20 791,-20 649,-20 526,10 423,69 319,128 240,212 186,322 131,431 104,561 104,711 104,936 165,1112 286,1239 407,1366 575,1430 790,1430 940,1430 1065,1401 1166,1342 1267,1283 1341,1196 1388,1081 L 1207,1021 C 1174,1103 1122,1166 1050,1209 977,1252 891,1274 792,1274 Z"/>
|
||||
<glyph unicode="A" horiz-adv-x="1372" d="M 1167,0 L 1006,412 364,412 202,0 4,0 579,1409 796,1409 1362,0 1167,0 Z M 685,1265 L 676,1237 C 659,1182 635,1111 602,1024 L 422,561 949,561 768,1026 C 749,1072 731,1124 712,1182 L 685,1265 Z"/>
|
||||
<glyph unicode="9" horiz-adv-x="958" d="M 1042,733 C 1042,491 998,305 910,175 821,45 695,-20 532,-20 422,-20 334,3 268,50 201,96 154,171 125,274 L 297,301 C 333,184 412,125 535,125 638,125 718,173 775,269 832,365 861,502 864,680 837,620 792,572 727,536 662,499 591,481 514,481 387,481 286,524 210,611 134,698 96,813 96,956 96,1103 137,1219 220,1304 303,1388 418,1430 565,1430 722,1430 840,1372 921,1256 1002,1140 1042,966 1042,733 Z M 846,907 C 846,1020 820,1112 768,1181 716,1250 646,1284 559,1284 472,1284 404,1255 354,1196 304,1137 279,1057 279,956 279,853 304,772 354,713 404,653 472,623 557,623 609,623 657,635 702,659 747,682 782,716 808,759 833,802 846,852 846,907 Z"/>
|
||||
<glyph unicode="8" horiz-adv-x="976" d="M 1050,393 C 1050,263 1009,162 926,89 843,16 725,-20 570,-20 419,-20 302,16 217,87 132,158 89,260 89,391 89,483 115,560 168,623 221,686 288,724 370,737 L 370,741 C 293,759 233,798 189,858 144,918 122,988 122,1069 122,1176 162,1263 243,1330 323,1397 431,1430 566,1430 705,1430 814,1397 895,1332 975,1267 1015,1178 1015,1067 1015,986 993,916 948,856 903,796 842,758 765,743 L 765,739 C 855,724 925,686 975,625 1025,563 1050,486 1050,393 Z M 828,1057 C 828,1216 741,1296 566,1296 481,1296 417,1276 373,1236 328,1196 306,1136 306,1057 306,976 329,915 375,873 420,830 485,809 568,809 653,809 717,829 762,868 806,907 828,970 828,1057 Z M 863,410 C 863,497 837,563 785,608 733,652 660,674 566,674 475,674 403,650 352,603 301,555 275,489 275,406 275,212 374,115 572,115 670,115 743,139 791,186 839,233 863,307 863,410 Z"/>
|
||||
<glyph unicode="7" horiz-adv-x="940" d="M 1036,1263 C 892,1043 790,871 731,746 672,621 627,498 598,377 568,256 553,130 553,0 L 365,0 C 365,180 403,370 480,569 556,768 683,997 862,1256 L 105,1256 105,1409 1036,1409 1036,1263 Z"/>
|
||||
<glyph unicode="6" horiz-adv-x="958" d="M 1049,461 C 1049,312 1009,195 928,109 847,23 736,-20 594,-20 435,-20 314,39 230,157 146,275 104,447 104,672 104,916 148,1103 235,1234 322,1365 447,1430 608,1430 821,1430 955,1334 1010,1143 L 838,1112 C 803,1227 725,1284 606,1284 503,1284 424,1236 368,1141 311,1045 283,906 283,725 316,786 362,832 421,864 480,895 548,911 625,911 755,911 858,870 935,789 1011,708 1049,598 1049,461 Z M 866,453 C 866,555 841,634 791,689 741,744 671,772 582,772 498,772 430,748 379,699 327,650 301,582 301,496 301,387 328,298 382,229 435,160 504,125 588,125 675,125 743,154 792,213 841,271 866,351 866,453 Z"/>
|
||||
<glyph unicode="5" horiz-adv-x="985" d="M 1053,459 C 1053,310 1009,193 921,108 832,23 710,-20 553,-20 422,-20 316,9 235,66 154,123 103,206 82,315 L 264,336 C 302,197 400,127 557,127 654,127 729,156 784,215 839,273 866,353 866,455 866,544 839,615 784,670 729,725 654,752 561,752 512,752 467,744 425,729 383,714 341,688 299,651 L 123,651 170,1409 971,1409 971,1256 334,1256 307,809 C 385,869 482,899 598,899 737,899 847,858 930,777 1012,696 1053,590 1053,459 Z"/>
|
||||
<glyph unicode="4" horiz-adv-x="1041" d="M 881,319 L 881,0 711,0 711,319 47,319 47,459 692,1409 881,1409 881,461 1079,461 1079,319 881,319 Z M 711,1206 C 710,1202 700,1184 683,1153 666,1122 653,1100 644,1087 L 283,555 229,481 213,461 711,461 711,1206 Z"/>
|
||||
<glyph unicode="3" horiz-adv-x="985" d="M 1049,389 C 1049,259 1008,158 925,87 842,16 724,-20 571,-20 428,-20 315,12 230,77 145,141 94,236 78,362 L 264,379 C 288,212 390,129 571,129 662,129 733,151 785,196 836,241 862,307 862,395 862,472 833,532 774,575 715,618 629,639 518,639 L 416,639 416,795 514,795 C 613,795 689,817 744,860 798,903 825,962 825,1038 825,1113 803,1173 759,1217 714,1260 648,1282 561,1282 482,1282 418,1262 369,1221 320,1180 291,1123 283,1049 L 102,1063 C 115,1178 163,1268 246,1333 328,1398 434,1430 563,1430 704,1430 814,1397 893,1332 971,1266 1010,1174 1010,1057 1010,967 985,894 935,838 884,781 811,743 715,723 L 715,719 C 820,708 902,672 961,613 1020,554 1049,479 1049,389 Z"/>
|
||||
<glyph unicode="2" horiz-adv-x="940" d="M 103,0 L 103,127 C 137,205 179,274 228,334 277,393 328,447 382,496 436,544 490,589 543,630 596,671 643,713 686,754 729,795 763,839 790,884 816,929 829,981 829,1038 829,1115 806,1175 761,1218 716,1261 653,1282 572,1282 495,1282 432,1261 383,1220 333,1178 304,1119 295,1044 L 111,1061 C 124,1174 172,1263 255,1330 337,1397 443,1430 572,1430 714,1430 823,1397 900,1330 976,1263 1014,1167 1014,1044 1014,989 1002,935 977,881 952,827 914,773 865,719 816,665 721,581 582,468 505,405 444,349 399,299 354,248 321,200 301,153 L 1036,153 1036,0 103,0 Z"/>
|
||||
<glyph unicode="1" horiz-adv-x="903" d="M 156,0 L 156,153 515,153 515,1237 197,1010 197,1180 530,1409 696,1409 696,153 1039,153 1039,0 156,0 Z"/>
|
||||
<glyph unicode="0" horiz-adv-x="995" d="M 1059,705 C 1059,470 1018,290 935,166 852,42 729,-20 567,-20 405,-20 283,42 202,165 121,288 80,468 80,705 80,947 120,1128 199,1249 278,1370 402,1430 573,1430 739,1430 862,1369 941,1247 1020,1125 1059,944 1059,705 Z M 876,705 C 876,908 853,1056 806,1147 759,1238 681,1284 573,1284 462,1284 383,1239 335,1149 286,1059 262,911 262,705 262,505 287,359 336,266 385,173 462,127 569,127 675,127 753,174 802,269 851,364 876,509 876,705 Z"/>
|
||||
<glyph unicode="-" horiz-adv-x="516" d="M 91,464 L 91,624 591,624 591,464 91,464 Z"/>
|
||||
<glyph unicode=" " horiz-adv-x="571"/>
|
||||
</font>
|
||||
</defs>
|
||||
<defs>
|
||||
<font id="EmbeddedFont_2" horiz-adv-x="2048">
|
||||
<font-face font-family="Liberation Sans embedded" units-per-em="2048" font-weight="bold" font-style="normal" ascent="1859" descent="433"/>
|
||||
<missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
|
||||
<glyph unicode="w" horiz-adv-x="1611" d="M 1313,0 L 1016,0 844,660 C 836,690 820,764 797,882 L 745,658 571,0 274,0 -6,1082 258,1082 436,255 450,329 475,446 645,1082 946,1082 1112,446 C 1121,411 1135,348 1153,255 L 1181,387 1337,1082 1597,1082 1313,0 Z"/>
|
||||
<glyph unicode="u" horiz-adv-x="1004" d="M 408,1082 L 408,475 C 408,285 472,190 600,190 668,190 723,219 765,278 806,336 827,411 827,502 L 827,1082 1108,1082 1108,242 C 1108,150 1111,69 1116,0 L 848,0 C 840,96 836,168 836,215 L 831,215 C 794,133 746,73 689,36 631,-1 562,-20 483,-20 368,-20 280,15 219,86 158,156 127,259 127,395 L 127,1082 408,1082 Z"/>
|
||||
<glyph unicode="s" horiz-adv-x="995" d="M 1055,316 C 1055,211 1012,129 927,70 841,10 722,-20 571,-20 422,-20 309,4 230,51 151,98 98,171 72,270 L 319,307 C 333,256 357,219 392,198 426,177 486,166 571,166 650,166 707,176 743,196 779,216 797,247 797,290 797,325 783,352 754,373 725,393 675,410 606,424 447,455 340,485 285,512 230,539 188,574 159,617 130,660 115,712 115,775 115,878 155,959 235,1017 314,1074 427,1103 573,1103 702,1103 805,1078 884,1028 962,978 1011,906 1030,811 L 781,785 C 773,829 753,862 722,884 691,905 641,916 573,916 506,916 456,908 423,891 390,874 373,845 373,805 373,774 386,749 412,731 437,712 480,697 541,685 626,668 701,650 767,632 832,613 885,591 925,566 964,541 996,508 1020,469 1043,429 1055,378 1055,316 Z"/>
|
||||
<glyph unicode="r" horiz-adv-x="645" d="M 143,0 L 143,828 C 143,887 142,937 141,977 139,1016 137,1051 135,1082 L 403,1082 C 405,1070 408,1034 411,973 414,912 416,871 416,851 L 420,851 C 447,927 472,981 493,1012 514,1043 540,1066 569,1081 598,1096 635,1103 679,1103 715,1103 744,1098 766,1088 L 766,853 C 721,863 681,868 646,868 576,868 522,840 483,783 444,726 424,642 424,531 L 424,0 143,0 Z"/>
|
||||
<glyph unicode="p" horiz-adv-x="1050" d="M 1167,546 C 1167,365 1131,226 1059,128 986,29 884,-20 752,-20 676,-20 610,-3 554,30 497,63 454,110 424,172 L 418,172 C 422,152 424,91 424,-10 L 424,-425 143,-425 143,833 C 143,935 140,1018 135,1082 L 408,1082 C 411,1070 414,1046 417,1011 419,976 420,941 420,906 L 424,906 C 487,1039 603,1105 770,1105 896,1105 994,1057 1063,960 1132,863 1167,725 1167,546 Z M 874,546 C 874,789 800,910 651,910 576,910 519,877 480,812 440,747 420,655 420,538 420,421 440,331 480,268 519,204 576,172 649,172 799,172 874,297 874,546 Z"/>
|
||||
<glyph unicode="o" horiz-adv-x="1105" d="M 1171,542 C 1171,367 1122,229 1025,130 928,30 793,-20 621,-20 452,-20 320,30 224,130 128,230 80,367 80,542 80,716 128,853 224,953 320,1052 454,1102 627,1102 804,1102 939,1054 1032,958 1125,861 1171,723 1171,542 Z M 877,542 C 877,671 856,764 814,822 772,880 711,909 631,909 460,909 375,787 375,542 375,421 396,330 438,267 479,204 539,172 618,172 791,172 877,295 877,542 Z"/>
|
||||
<glyph unicode="m" horiz-adv-x="1575" d="M 780,0 L 780,607 C 780,797 725,892 616,892 559,892 513,863 478,805 442,747 424,672 424,580 L 424,0 143,0 143,840 C 143,898 142,946 141,983 139,1020 137,1053 135,1082 L 403,1082 C 405,1069 408,1036 411,981 414,926 416,888 416,867 L 420,867 C 455,950 498,1010 550,1047 601,1084 663,1103 735,1103 900,1103 1001,1024 1036,867 L 1042,867 C 1079,951 1123,1011 1174,1048 1225,1085 1291,1103 1370,1103 1475,1103 1556,1067 1611,996 1666,924 1694,821 1694,687 L 1694,0 1415,0 1415,607 C 1415,797 1360,892 1251,892 1196,892 1152,866 1117,813 1082,760 1062,686 1059,593 L 1059,0 780,0 Z"/>
|
||||
<glyph unicode="l" horiz-adv-x="296" d="M 143,0 L 143,1484 424,1484 424,0 143,0 Z"/>
|
||||
<glyph unicode="e" horiz-adv-x="1004" d="M 586,-20 C 423,-20 298,28 211,125 124,221 80,361 80,546 80,725 124,862 213,958 302,1054 427,1102 590,1102 745,1102 864,1051 946,948 1028,845 1069,694 1069,495 L 1069,487 375,487 C 375,382 395,302 434,249 473,195 528,168 600,168 699,168 762,211 788,297 L 1053,274 C 976,78 821,-20 586,-20 Z M 586,925 C 520,925 469,902 434,856 398,810 379,746 377,663 L 797,663 C 792,750 771,816 734,860 697,903 648,925 586,925 Z"/>
|
||||
<glyph unicode="d" horiz-adv-x="1041" d="M 844,0 C 841,10 838,35 835,76 831,116 829,149 829,176 L 825,176 C 764,45 649,-20 479,-20 353,-20 256,29 187,128 118,226 84,363 84,540 84,719 120,858 193,956 265,1053 367,1102 500,1102 577,1102 643,1086 699,1054 754,1022 797,974 827,911 L 829,911 827,1089 827,1484 1108,1484 1108,236 C 1108,169 1111,91 1116,0 L 844,0 Z M 831,547 C 831,664 812,754 773,817 734,880 676,911 600,911 525,911 469,881 432,820 395,759 377,665 377,540 377,295 451,172 598,172 672,172 729,205 770,270 811,335 831,427 831,547 Z"/>
|
||||
<glyph unicode="c" horiz-adv-x="1013" d="M 594,-20 C 430,-20 303,29 214,127 125,224 80,360 80,535 80,714 125,853 215,953 305,1052 433,1102 598,1102 725,1102 831,1070 914,1006 997,942 1050,854 1071,741 L 788,727 C 780,782 760,827 728,860 696,893 651,909 592,909 447,909 375,788 375,546 375,297 449,172 596,172 649,172 694,189 730,223 766,256 788,306 797,373 L 1079,360 C 1069,286 1043,220 1000,162 957,104 900,59 830,28 760,-4 681,-20 594,-20 Z"/>
|
||||
<glyph unicode="a" horiz-adv-x="1106" d="M 393,-20 C 288,-20 207,9 148,66 89,123 60,203 60,306 60,418 97,503 170,562 243,621 348,651 487,652 L 720,656 720,711 C 720,782 708,834 683,869 658,903 618,920 562,920 510,920 472,908 448,885 423,861 408,822 402,767 L 109,781 C 127,886 175,966 254,1021 332,1075 439,1102 574,1102 711,1102 816,1068 890,1001 964,934 1001,838 1001,714 L 1001,320 C 1001,259 1008,218 1022,195 1035,172 1058,160 1090,160 1111,160 1132,162 1152,166 L 1152,14 C 1135,10 1120,6 1107,3 1094,0 1080,-3 1067,-5 1054,-7 1040,-9 1025,-10 1010,-11 992,-12 972,-12 901,-12 849,5 816,40 782,75 762,126 755,193 L 749,193 C 670,51 552,-20 393,-20 Z M 720,501 L 576,499 C 511,496 464,489 437,478 410,466 389,448 375,424 360,400 353,368 353,328 353,277 365,239 389,214 412,189 444,176 483,176 527,176 567,188 604,212 640,236 668,269 689,312 710,354 720,399 720,446 L 720,501 Z"/>
|
||||
<glyph unicode="S" horiz-adv-x="1244" d="M 1286,406 C 1286,268 1235,163 1133,90 1030,17 880,-20 682,-20 501,-20 360,12 257,76 154,140 88,237 59,367 L 344,414 C 363,339 401,285 457,252 513,218 591,201 690,201 896,201 999,264 999,389 999,429 987,462 964,488 940,514 907,536 864,553 821,570 738,591 616,616 511,641 437,661 396,676 355,691 317,708 284,729 251,749 222,773 199,802 176,831 158,864 145,903 132,942 125,986 125,1036 125,1163 173,1261 269,1329 364,1396 503,1430 686,1430 861,1430 992,1403 1080,1348 1167,1293 1224,1203 1249,1077 L 963,1038 C 948,1099 919,1144 874,1175 829,1206 764,1221 680,1221 501,1221 412,1165 412,1053 412,1016 422,986 441,963 460,940 488,920 525,904 562,887 638,867 752,842 887,813 984,787 1043,763 1101,738 1147,710 1181,678 1215,645 1241,607 1259,562 1277,517 1286,465 1286,406 Z"/>
|
||||
<glyph unicode=" " horiz-adv-x="571"/>
|
||||
</font>
|
||||
</defs>
|
||||
<defs class="TextShapeIndex">
|
||||
<g ooo:slide="id1" ooo:id-list="id3 id4 id5 id6 id7 id8 id9 id10 id11 id12 id13 id14 id15 id16 id17 id18 id19 id20 id21 id22 id23 id24 id25 id26 id27 id28 id29 id30 id31 id32 id33 id34 id35 id36 id37 id38 id39 id40 id41 id42 id43 id44 id45"/>
|
||||
</defs>
|
||||
<defs class="EmbeddedBulletChars">
|
||||
<g id="bullet-char-template-57356" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-57354" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-10146" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-10132" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-10007" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-10004" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-9679" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-8226" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-8211" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-61548" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"/>
|
||||
</g>
|
||||
</defs>
|
||||
<defs class="TextEmbeddedBitmaps"/>
|
||||
<g>
|
||||
<g id="id2" class="Master_Slide">
|
||||
<g id="bg-id2" class="Background"/>
|
||||
<g id="bo-id2" class="BackgroundObjects"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="SlideGroup">
|
||||
<g>
|
||||
<g id="container-id1">
|
||||
<g id="id1" class="Slide" clip-path="url(#presentation_clip_path)">
|
||||
<g class="Page">
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id3">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="8996" y="2598" width="1782" height="2671"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 8997,2599 L 10776,2599 10776,4933 10553,5267 8997,5267 8997,2599 Z M 8997,2599 L 8997,2599 Z M 10776,5267 L 10776,5267 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8997,2599 L 10776,2599 10776,4933 10553,5267 8997,5267 8997,2599 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8997,2599 L 8997,2599 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10776,5267 L 10776,5267 Z"/>
|
||||
<path fill="rgb(91,127,166)" stroke="none" d="M 10553,5267 L 10611,4933 C 10666,4981 10664,4954 10776,4933 L 10553,5267 Z M 8997,2599 L 8997,2599 Z M 10776,5267 L 10776,5267 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10553,5267 L 10611,4933 C 10666,4981 10664,4954 10776,4933 L 10553,5267 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8997,2599 L 8997,2599 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10776,5267 L 10776,5267 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id4">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9567" y="5346" width="639" height="1247"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 9727,5347 L 9727,6280 9568,6280 9886,6591 10204,6280 10045,6280 10045,5347 9727,5347 Z M 9568,5347 L 9568,5347 Z M 10204,6591 L 10204,6591 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9727,5347 L 9727,6280 9568,6280 9886,6591 10204,6280 10045,6280 10045,5347 9727,5347 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9568,5347 L 9568,5347 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10204,6591 L 10204,6591 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id5">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="2177" y="6310" width="17113" height="8407"/>
|
||||
<path fill="none" stroke="rgb(245,130,32)" d="M 10733,14665 L 2178,14616 2178,6311 19288,6410 19288,14715 10733,14665 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id6">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="6078" y="9478" width="1401" height="1401"/>
|
||||
<path fill="rgb(245,130,32)" stroke="none" d="M 6079,10877 L 6079,9828 6428,9479 7477,9479 7477,10527 7127,10877 6079,10877 Z M 6079,9479 L 6079,9479 Z M 7477,10877 L 7477,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 6079,10877 L 6079,9828 6428,9479 7477,9479 7477,10527 7127,10877 6079,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 6079,9479 L 6079,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7477,10877 L 7477,10877 Z"/>
|
||||
<path fill="rgb(247,154,75)" stroke="none" d="M 6079,9828 L 6428,9479 7477,9479 7127,9828 6079,9828 Z M 6079,9479 L 6079,9479 Z M 7477,10877 L 7477,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 6079,9828 L 6428,9479 7477,9479 7127,9828 6079,9828 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 6079,9479 L 6079,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7477,10877 L 7477,10877 Z"/>
|
||||
<path fill="rgb(196,104,26)" stroke="none" d="M 7127,10877 L 7127,9828 7477,9479 7477,10527 7127,10877 Z M 6079,9479 L 6079,9479 Z M 7477,10877 L 7477,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7127,10877 L 7127,9828 7477,9479 7477,10527 7127,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 6079,9479 L 6079,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7477,10877 L 7477,10877 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id7">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="7778" y="9478" width="1401" height="1401"/>
|
||||
<path fill="rgb(245,130,32)" stroke="none" d="M 7779,10877 L 7779,9828 8128,9479 9177,9479 9177,10527 8827,10877 7779,10877 Z M 7779,9479 L 7779,9479 Z M 9177,10877 L 9177,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7779,10877 L 7779,9828 8128,9479 9177,9479 9177,10527 8827,10877 7779,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7779,9479 L 7779,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9177,10877 L 9177,10877 Z"/>
|
||||
<path fill="rgb(247,154,75)" stroke="none" d="M 7779,9828 L 8128,9479 9177,9479 8827,9828 7779,9828 Z M 7779,9479 L 7779,9479 Z M 9177,10877 L 9177,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7779,9828 L 8128,9479 9177,9479 8827,9828 7779,9828 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7779,9479 L 7779,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9177,10877 L 9177,10877 Z"/>
|
||||
<path fill="rgb(196,104,26)" stroke="none" d="M 8827,10877 L 8827,9828 9177,9479 9177,10527 8827,10877 Z M 7779,9479 L 7779,9479 Z M 9177,10877 L 9177,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8827,10877 L 8827,9828 9177,9479 9177,10527 8827,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7779,9479 L 7779,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9177,10877 L 9177,10877 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id8">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9578" y="9478" width="1401" height="1401"/>
|
||||
<path fill="rgb(245,130,32)" stroke="none" d="M 9579,10877 L 9579,9828 9928,9479 10977,9479 10977,10527 10627,10877 9579,10877 Z M 9579,9479 L 9579,9479 Z M 10977,10877 L 10977,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9579,10877 L 9579,9828 9928,9479 10977,9479 10977,10527 10627,10877 9579,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9579,9479 L 9579,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10977,10877 L 10977,10877 Z"/>
|
||||
<path fill="rgb(247,154,75)" stroke="none" d="M 9579,9828 L 9928,9479 10977,9479 10627,9828 9579,9828 Z M 9579,9479 L 9579,9479 Z M 10977,10877 L 10977,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9579,9828 L 9928,9479 10977,9479 10627,9828 9579,9828 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9579,9479 L 9579,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10977,10877 L 10977,10877 Z"/>
|
||||
<path fill="rgb(196,104,26)" stroke="none" d="M 10627,10877 L 10627,9828 10977,9479 10977,10527 10627,10877 Z M 9579,9479 L 9579,9479 Z M 10977,10877 L 10977,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10627,10877 L 10627,9828 10977,9479 10977,10527 10627,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9579,9479 L 9579,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10977,10877 L 10977,10877 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id9">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11378" y="9478" width="1401" height="1401"/>
|
||||
<path fill="rgb(245,130,32)" stroke="none" d="M 11379,10877 L 11379,9828 11728,9479 12777,9479 12777,10527 12427,10877 11379,10877 Z M 11379,9479 L 11379,9479 Z M 12777,10877 L 12777,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 11379,10877 L 11379,9828 11728,9479 12777,9479 12777,10527 12427,10877 11379,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 11379,9479 L 11379,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 12777,10877 L 12777,10877 Z"/>
|
||||
<path fill="rgb(247,154,75)" stroke="none" d="M 11379,9828 L 11728,9479 12777,9479 12427,9828 11379,9828 Z M 11379,9479 L 11379,9479 Z M 12777,10877 L 12777,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 11379,9828 L 11728,9479 12777,9479 12427,9828 11379,9828 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 11379,9479 L 11379,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 12777,10877 L 12777,10877 Z"/>
|
||||
<path fill="rgb(196,104,26)" stroke="none" d="M 12427,10877 L 12427,9828 12777,9479 12777,10527 12427,10877 Z M 11379,9479 L 11379,9479 Z M 12777,10877 L 12777,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 12427,10877 L 12427,9828 12777,9479 12777,10527 12427,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 11379,9479 L 11379,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 12777,10877 L 12777,10877 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id10">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="13078" y="9478" width="1401" height="1401"/>
|
||||
<path fill="rgb(245,130,32)" stroke="none" d="M 13079,10877 L 13079,9828 13428,9479 14477,9479 14477,10527 14127,10877 13079,10877 Z M 13079,9479 L 13079,9479 Z M 14477,10877 L 14477,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 13079,10877 L 13079,9828 13428,9479 14477,9479 14477,10527 14127,10877 13079,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 13079,9479 L 13079,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 14477,10877 L 14477,10877 Z"/>
|
||||
<path fill="rgb(247,154,75)" stroke="none" d="M 13079,9828 L 13428,9479 14477,9479 14127,9828 13079,9828 Z M 13079,9479 L 13079,9479 Z M 14477,10877 L 14477,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 13079,9828 L 13428,9479 14477,9479 14127,9828 13079,9828 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 13079,9479 L 13079,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 14477,10877 L 14477,10877 Z"/>
|
||||
<path fill="rgb(196,104,26)" stroke="none" d="M 14127,10877 L 14127,9828 14477,9479 14477,10527 14127,10877 Z M 13079,9479 L 13079,9479 Z M 14477,10877 L 14477,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 14127,10877 L 14127,9828 14477,9479 14477,10527 14127,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 13079,9479 L 13079,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 14477,10877 L 14477,10877 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id11">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="4278" y="9478" width="1401" height="1401"/>
|
||||
<path fill="rgb(245,130,32)" stroke="none" d="M 4279,10877 L 4279,9828 4628,9479 5677,9479 5677,10527 5327,10877 4279,10877 Z M 4279,9479 L 4279,9479 Z M 5677,10877 L 5677,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4279,10877 L 4279,9828 4628,9479 5677,9479 5677,10527 5327,10877 4279,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4279,9479 L 4279,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 5677,10877 L 5677,10877 Z"/>
|
||||
<path fill="rgb(247,154,75)" stroke="none" d="M 4279,9828 L 4628,9479 5677,9479 5327,9828 4279,9828 Z M 4279,9479 L 4279,9479 Z M 5677,10877 L 5677,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4279,9828 L 4628,9479 5677,9479 5327,9828 4279,9828 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4279,9479 L 4279,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 5677,10877 L 5677,10877 Z"/>
|
||||
<path fill="rgb(196,104,26)" stroke="none" d="M 5327,10877 L 5327,9828 5677,9479 5677,10527 5327,10877 Z M 4279,9479 L 4279,9479 Z M 5677,10877 L 5677,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 5327,10877 L 5327,9828 5677,9479 5677,10527 5327,10877 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4279,9479 L 4279,9479 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 5677,10877 L 5677,10877 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id12">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="15322" y="11853" width="2671" height="2417"/>
|
||||
<path fill="rgb(245,130,32)" stroke="none" d="M 16657,11854 C 15929,11854 15323,11991 15323,12155 L 15323,13966 C 15323,14130 15929,14268 16657,14268 17384,14268 17991,14130 17991,13966 L 17991,12155 C 17991,11991 17384,11854 16657,11854 L 16657,11854 Z M 15323,11854 L 15323,11854 Z M 17991,14268 L 17991,14268 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 16657,11854 C 15929,11854 15323,11991 15323,12155 L 15323,13966 C 15323,14130 15929,14268 16657,14268 17384,14268 17991,14130 17991,13966 L 17991,12155 C 17991,11991 17384,11854 16657,11854 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 15323,11854 L 15323,11854 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 17991,14268 L 17991,14268 Z"/>
|
||||
<path fill="rgb(249,179,119)" stroke="none" d="M 16657,11854 C 15929,11854 15323,11991 15323,12155 15323,12320 15929,12457 16657,12457 17384,12457 17991,12320 17991,12155 17991,11991 17384,11854 16657,11854 L 16657,11854 Z M 15323,11854 L 15323,11854 Z M 17991,14268 L 17991,14268 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 16657,11854 C 15929,11854 15323,11991 15323,12155 15323,12320 15929,12457 16657,12457 17384,12457 17991,12320 17991,12155 17991,11991 17384,11854 16657,11854 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 15323,11854 L 15323,11854 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 17991,14268 L 17991,14268 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="15902" y="13077"><tspan fill="rgb(0,0,0)" stroke="none">Local</tspan></tspan><tspan class="TextPosition" x="15899" y="13788"><tspan fill="rgb(0,0,0)" stroke="none">Store</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id13">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="15033" y="9875" width="1920" height="1879"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" stroke-width="81" stroke-linejoin="round" d="M 15074,9916 L 15074,9950 16741,9950 16741,11150"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 16741,11753 L 16952,11122 16531,11122 16741,11753 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id14">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="8996" y="6616" width="1782" height="1147"/>
|
||||
<path fill="rgb(245,130,32)" stroke="none" d="M 8997,6617 L 10776,6617 10776,7761 8997,7761 8997,6617 8997,6617 Z M 8997,6617 L 8997,6617 Z M 10776,7761 L 10776,7761 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8997,6617 L 10776,6617 10776,7761 8997,7761 8997,6617 8997,6617 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8997,6617 L 8997,6617 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10776,7761 L 10776,7761 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="9258" y="7409"><tspan fill="rgb(0,0,0)" stroke="none">DPA</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id15">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9189" y="7932" width="1365" height="1274"/>
|
||||
<path fill="rgb(245,130,32)" stroke="none" d="M 9993,8486 C 9993,8632 10107,8746 10239,8746 10239,8569 10239,8569 10239,8569 10552,8892 10552,8892 10552,8892 10239,9204 10239,9204 10239,9204 10239,9027 10239,9027 10239,9027 10088,9027 9956,8954 9861,8840 9776,8944 9644,9017 9492,9017 9492,9204 9492,9204 9492,9204 9190,8882 9190,8882 9190,8882 9492,8569 9492,8569 9492,8569 9492,8746 9492,8746 9492,8746 9625,8746 9738,8632 9738,8486 9738,7933 9738,7933 9738,7933 L 9738,7933 C 9993,7933 9993,7933 9993,7933 L 9993,7933 9993,8486 9993,8486 Z M 9190,9204 L 9190,9204 Z M 10552,7933 L 10552,7933 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9993,8486 C 9993,8632 10107,8746 10239,8746 L 10239,8569 10552,8892 10239,9204 10239,9027 C 10088,9027 9956,8954 9861,8840 9776,8944 9644,9017 9492,9017 L 9492,9204 9190,8882 9492,8569 9492,8746 C 9625,8746 9738,8632 9738,8486 L 9738,7933 9738,7933 9993,7933 9993,7933 9993,8486 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9190,9204 L 9190,9204 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10552,7933 L 10552,7933 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id16">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="999" y="15644" width="19594" height="2263"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 1000,16775 L 4900,15645 4900,16210 16690,16210 16690,15645 20591,16775 16690,17905 16690,17340 4900,17340 4900,17905 1000,16775 Z M 1000,15645 L 1000,15645 Z M 20591,17905 L 20591,17905 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 1000,16775 L 4900,15645 4900,16210 16690,16210 16690,15645 20591,16775 16690,17905 16690,17340 4900,17340 4900,17905 1000,16775 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 1000,15645 L 1000,15645 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 20591,17905 L 20591,17905 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8471" y="16996"><tspan fill="rgb(0,0,0)" stroke="none">Syncing process</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TableShape">
|
||||
<g>
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="2197" y="19611" width="17786" height="8591"/>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 2199,19613 L 3310,19613 3310,23903 2199,23903 2199,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="2449" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 3310,19613 L 4421,19613 4421,23903 3310,23903 3310,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="3560" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 4421,19613 L 5532,19613 5532,23903 4421,23903 4421,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="4671" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">2</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 5532,19613 L 6643,19613 6643,23903 5532,23903 5532,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="5782" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">3</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 6643,19613 L 7754,19613 7754,23903 6643,23903 6643,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="6893" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">4</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 7754,19613 L 8865,19613 8865,23903 7754,23903 7754,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="8004" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">5</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 8865,19613 L 9976,19613 9976,23903 8865,23903 8865,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="9115" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">6</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 9976,19613 L 11087,19613 11087,23903 9976,23903 9976,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="10226" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">7</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 11087,19613 L 12198,19613 12198,23903 11087,23903 11087,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="11337" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">8</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 12198,19613 L 13309,19613 13309,23903 12198,23903 12198,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="12448" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">9</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 13309,19613 L 14420,19613 14420,23903 13309,23903 13309,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="13559" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">10</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 14420,19613 L 15531,19613 15531,23903 14420,23903 14420,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="14670" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">11</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 15531,19613 L 16642,19613 16642,23903 15531,23903 15531,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="15781" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">12</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 16642,19613 L 17753,19613 17753,23903 16642,23903 16642,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="16892" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">13</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 17753,19613 L 18864,19613 18864,23903 17753,23903 17753,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="18003" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">14</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 18864,19613 L 19980,19613 19980,23903 18864,23903 18864,19613 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="19114" y="23667"><tspan fill="rgb(0,0,0)" stroke="none">15</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 2199,23903 L 3310,23903 3310,28199 2199,28199 2199,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="2449" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">16</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 3310,23903 L 4421,23903 4421,28199 3310,28199 3310,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="3560" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">17</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 4421,23903 L 5532,23903 5532,28199 4421,28199 4421,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="4671" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">18</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 5532,23903 L 6643,23903 6643,28199 5532,28199 5532,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="5782" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">19</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 6643,23903 L 7754,23903 7754,28199 6643,28199 6643,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="6893" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">20</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 7754,23903 L 8865,23903 8865,28199 7754,28199 7754,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="8004" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">21</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 8865,23903 L 9976,23903 9976,28199 8865,28199 8865,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="9115" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">22</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 9976,23903 L 11087,23903 11087,28199 9976,28199 9976,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="10226" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">23</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 11087,23903 L 12198,23903 12198,28199 11087,28199 11087,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="11337" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">24</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 12198,23903 L 13309,23903 13309,28199 12198,28199 12198,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="12448" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">25</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 13309,23903 L 14420,23903 14420,28199 13309,28199 13309,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="13559" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">26</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 14420,23903 L 15531,23903 15531,28199 14420,28199 14420,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="14670" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">27</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 15531,23903 L 16642,23903 16642,28199 15531,28199 15531,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="15781" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">28</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 16642,23903 L 17753,23903 17753,28199 16642,28199 16642,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="16892" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">29</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 17753,23903 L 18864,23903 18864,28199 17753,28199 17753,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="18003" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">30</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(252,199,155)" stroke="none" d="M 18864,23903 L 19980,23903 19980,28199 18864,28199 18864,23903 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="19114" y="27963"><tspan fill="rgb(0,0,0)" stroke="none">31</tspan></tspan></tspan></text>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2198,19613 L 19981,19613"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2198,23903 L 3311,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2198,28199 L 3311,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3309,23903 L 4422,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3309,28199 L 4422,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4420,23903 L 5533,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4420,28199 L 5533,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5531,23903 L 6644,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5531,28199 L 6644,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6642,23903 L 7755,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6642,28199 L 7755,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7753,23903 L 8866,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7753,28199 L 8866,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8864,23903 L 9977,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8864,28199 L 9977,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9975,23903 L 11088,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9975,28199 L 11088,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11086,23903 L 12199,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11086,28199 L 12199,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12197,23903 L 13310,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12197,28199 L 13310,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13308,23903 L 14421,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13308,28199 L 14421,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14419,23903 L 15532,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14419,28199 L 15532,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15530,23903 L 16643,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15530,28199 L 16643,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 16641,23903 L 17754,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 16641,28199 L 17754,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 17752,23903 L 18865,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 17752,28199 L 18865,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 18863,23903 L 19981,23903"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 18863,28199 L 19981,28199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 2199,19612 L 2199,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3310,19612 L 3310,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4421,19612 L 4421,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5532,19612 L 5532,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6643,19612 L 6643,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7754,19612 L 7754,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8865,19612 L 8865,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9976,19612 L 9976,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11087,19612 L 11087,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12198,19612 L 12198,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13309,19612 L 13309,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14420,19612 L 14420,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15531,19612 L 15531,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 16642,19612 L 16642,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 17753,19612 L 17753,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 18864,19612 L 18864,23904"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 19980,19612 L 19980,23904"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 18864,23902 L 18864,28200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 19980,23902 L 19980,28200"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id17">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="2316" y="20711" width="512" height="512"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 2571,20712 C 2715,20712 2825,20822 2825,20966 2825,21110 2715,21220 2571,21220 2427,21220 2317,21110 2317,20966 2317,20822 2427,20712 2571,20712 Z M 2317,20712 L 2317,20712 Z M 2826,21221 L 2826,21221 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2571,20712 C 2715,20712 2825,20822 2825,20966 2825,21110 2715,21220 2571,21220 2427,21220 2317,21110 2317,20966 2317,20822 2427,20712 2571,20712 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2317,20712 L 2317,20712 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2826,21221 L 2826,21221 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id18">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="3459" y="19903" width="512" height="512"/>
|
||||
<path fill="rgb(57,10,93)" stroke="none" d="M 3714,19904 C 3858,19904 3968,20014 3968,20158 3968,20302 3858,20412 3714,20412 3570,20412 3460,20302 3460,20158 3460,20014 3570,19904 3714,19904 Z M 3460,19904 L 3460,19904 Z M 3969,20413 L 3969,20413 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 3714,19904 C 3858,19904 3968,20014 3968,20158 3968,20302 3858,20412 3714,20412 3570,20412 3460,20302 3460,20158 3460,20014 3570,19904 3714,19904 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 3460,19904 L 3460,19904 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 3969,20413 L 3969,20413 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id19">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="3459" y="20711" width="512" height="512"/>
|
||||
<path fill="rgb(57,10,93)" stroke="none" d="M 3714,20712 C 3858,20712 3968,20822 3968,20966 3968,21110 3858,21220 3714,21220 3570,21220 3460,21110 3460,20966 3460,20822 3570,20712 3714,20712 Z M 3460,20712 L 3460,20712 Z M 3969,21221 L 3969,21221 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 3714,20712 C 3858,20712 3968,20822 3968,20966 3968,21110 3858,21220 3714,21220 3570,21220 3460,21110 3460,20966 3460,20822 3570,20712 3714,20712 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 3460,20712 L 3460,20712 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 3969,21221 L 3969,21221 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id20">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="4575" y="19911" width="512" height="512"/>
|
||||
<path fill="rgb(0,166,93)" stroke="none" d="M 4830,19912 C 4974,19912 5084,20022 5084,20166 5084,20310 4974,20420 4830,20420 4686,20420 4576,20310 4576,20166 4576,20022 4686,19912 4830,19912 Z M 4576,19912 L 4576,19912 Z M 5085,20421 L 5085,20421 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4830,19912 C 4974,19912 5084,20022 5084,20166 5084,20310 4974,20420 4830,20420 4686,20420 4576,20310 4576,20166 4576,20022 4686,19912 4830,19912 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4576,19912 L 4576,19912 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 5085,20421 L 5085,20421 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id21">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="5745" y="19911" width="512" height="512"/>
|
||||
<path fill="rgb(237,28,36)" stroke="none" d="M 6000,19912 C 6144,19912 6254,20022 6254,20166 6254,20310 6144,20420 6000,20420 5856,20420 5746,20310 5746,20166 5746,20022 5856,19912 6000,19912 Z M 5746,19912 L 5746,19912 Z M 6255,20421 L 6255,20421 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 6000,19912 C 6144,19912 6254,20022 6254,20166 6254,20310 6144,20420 6000,20420 5856,20420 5746,20310 5746,20166 5746,20022 5856,19912 6000,19912 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 5746,19912 L 5746,19912 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 6255,20421 L 6255,20421 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id22">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="2316" y="21600" width="512" height="512"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 2571,21601 C 2715,21601 2825,21711 2825,21855 2825,21999 2715,22109 2571,22109 2427,22109 2317,21999 2317,21855 2317,21711 2427,21601 2571,21601 Z M 2317,21601 L 2317,21601 Z M 2826,22110 L 2826,22110 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2571,21601 C 2715,21601 2825,21711 2825,21855 2825,21999 2715,22109 2571,22109 2427,22109 2317,21999 2317,21855 2317,21711 2427,21601 2571,21601 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2317,21601 L 2317,21601 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2826,22110 L 2826,22110 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id23">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="2316" y="22489" width="512" height="512"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 2571,22490 C 2715,22490 2825,22600 2825,22744 2825,22888 2715,22998 2571,22998 2427,22998 2317,22888 2317,22744 2317,22600 2427,22490 2571,22490 Z M 2317,22490 L 2317,22490 Z M 2826,22999 L 2826,22999 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2571,22490 C 2715,22490 2825,22600 2825,22744 2825,22888 2715,22998 2571,22998 2427,22998 2317,22888 2317,22744 2317,22600 2427,22490 2571,22490 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2317,22490 L 2317,22490 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2826,22999 L 2826,22999 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id24">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="2316" y="19903" width="512" height="512"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 2571,19904 C 2715,19904 2825,20014 2825,20158 2825,20302 2715,20412 2571,20412 2427,20412 2317,20302 2317,20158 2317,20014 2427,19904 2571,19904 Z M 2317,19904 L 2317,19904 Z M 2826,20413 L 2826,20413 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2571,19904 C 2715,19904 2825,20014 2825,20158 2825,20302 2715,20412 2571,20412 2427,20412 2317,20302 2317,20158 2317,20014 2427,19904 2571,19904 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2317,19904 L 2317,19904 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2826,20413 L 2826,20413 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id25">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="4575" y="20727" width="512" height="512"/>
|
||||
<path fill="rgb(0,166,93)" stroke="none" d="M 4830,20728 C 4974,20728 5084,20838 5084,20982 5084,21126 4974,21236 4830,21236 4686,21236 4576,21126 4576,20982 4576,20838 4686,20728 4830,20728 Z M 4576,20728 L 4576,20728 Z M 5085,21237 L 5085,21237 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4830,20728 C 4974,20728 5084,20838 5084,20982 5084,21126 4974,21236 4830,21236 4686,21236 4576,21126 4576,20982 4576,20838 4686,20728 4830,20728 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4576,20728 L 4576,20728 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 5085,21237 L 5085,21237 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id26">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="4576" y="21612" width="512" height="512"/>
|
||||
<path fill="rgb(0,166,93)" stroke="none" d="M 4831,21613 C 4975,21613 5085,21723 5085,21867 5085,22011 4975,22121 4831,22121 4687,22121 4577,22011 4577,21867 4577,21723 4687,21613 4831,21613 Z M 4577,21613 L 4577,21613 Z M 5086,22122 L 5086,22122 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4831,21613 C 4975,21613 5085,21723 5085,21867 5085,22011 4975,22121 4831,22121 4687,22121 4577,22011 4577,21867 4577,21723 4687,21613 4831,21613 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4577,21613 L 4577,21613 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 5086,22122 L 5086,22122 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id27">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="6776" y="19912" width="512" height="512"/>
|
||||
<path fill="rgb(130,106,175)" stroke="none" d="M 7031,19913 C 7175,19913 7285,20023 7285,20167 7285,20311 7175,20421 7031,20421 6887,20421 6777,20311 6777,20167 6777,20023 6887,19913 7031,19913 Z M 6777,19913 L 6777,19913 Z M 7286,20422 L 7286,20422 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7031,19913 C 7175,19913 7285,20023 7285,20167 7285,20311 7175,20421 7031,20421 6887,20421 6777,20311 6777,20167 6777,20023 6887,19913 7031,19913 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 6777,19913 L 6777,19913 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7286,20422 L 7286,20422 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id28">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="6776" y="20728" width="512" height="512"/>
|
||||
<path fill="rgb(130,106,175)" stroke="none" d="M 7031,20729 C 7175,20729 7285,20839 7285,20983 7285,21127 7175,21237 7031,21237 6887,21237 6777,21127 6777,20983 6777,20839 6887,20729 7031,20729 Z M 6777,20729 L 6777,20729 Z M 7286,21238 L 7286,21238 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7031,20729 C 7175,20729 7285,20839 7285,20983 7285,21127 7175,21237 7031,21237 6887,21237 6777,21127 6777,20983 6777,20839 6887,20729 7031,20729 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 6777,20729 L 6777,20729 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7286,21238 L 7286,21238 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id29">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="6777" y="21613" width="512" height="512"/>
|
||||
<path fill="rgb(130,106,175)" stroke="none" d="M 7032,21614 C 7176,21614 7286,21724 7286,21868 7286,22012 7176,22122 7032,22122 6888,22122 6778,22012 6778,21868 6778,21724 6888,21614 7032,21614 Z M 6778,21614 L 6778,21614 Z M 7287,22123 L 7287,22123 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7032,21614 C 7176,21614 7286,21724 7286,21868 7286,22012 7176,22122 7032,22122 6888,22122 6778,22012 6778,21868 6778,21724 6888,21614 7032,21614 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 6778,21614 L 6778,21614 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7287,22123 L 7287,22123 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id30">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9060" y="19904" width="512" height="512"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 9315,19905 C 9459,19905 9569,20015 9569,20159 9569,20303 9459,20413 9315,20413 9171,20413 9061,20303 9061,20159 9061,20015 9171,19905 9315,19905 Z M 9061,19905 L 9061,19905 Z M 9570,20414 L 9570,20414 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9315,19905 C 9459,19905 9569,20015 9569,20159 9569,20303 9459,20413 9315,20413 9171,20413 9061,20303 9061,20159 9061,20015 9171,19905 9315,19905 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9061,19905 L 9061,19905 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9570,20414 L 9570,20414 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id31">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9060" y="20712" width="512" height="512"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 9315,20713 C 9459,20713 9569,20823 9569,20967 9569,21111 9459,21221 9315,21221 9171,21221 9061,21111 9061,20967 9061,20823 9171,20713 9315,20713 Z M 9061,20713 L 9061,20713 Z M 9570,21222 L 9570,21222 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9315,20713 C 9459,20713 9569,20823 9569,20967 9569,21111 9459,21221 9315,21221 9171,21221 9061,21111 9061,20967 9061,20823 9171,20713 9315,20713 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9061,20713 L 9061,20713 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9570,21222 L 9570,21222 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id32">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="7946" y="19912" width="512" height="512"/>
|
||||
<path fill="rgb(173,197,231)" stroke="none" d="M 8201,19913 C 8345,19913 8455,20023 8455,20167 8455,20311 8345,20421 8201,20421 8057,20421 7947,20311 7947,20167 7947,20023 8057,19913 8201,19913 Z M 7947,19913 L 7947,19913 Z M 8456,20422 L 8456,20422 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8201,19913 C 8345,19913 8455,20023 8455,20167 8455,20311 8345,20421 8201,20421 8057,20421 7947,20311 7947,20167 7947,20023 8057,19913 8201,19913 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7947,19913 L 7947,19913 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8456,20422 L 8456,20422 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TableShape">
|
||||
<g>
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="4148" y="11246" width="10698" height="977"/>
|
||||
<path fill="rgb(173,197,231)" stroke="none" d="M 4150,11248 L 5933,11248 5933,12220 4150,12220 4150,11248 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="4866" y="11954"><tspan fill="rgb(0,0,0)" stroke="none">5</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(237,28,36)" stroke="none" d="M 5933,11248 L 7716,11248 7716,12220 5933,12220 5933,11248 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="6649" y="11954"><tspan fill="rgb(0,0,0)" stroke="none">3</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(0,166,93)" stroke="none" d="M 7716,11248 L 9499,11248 9499,12220 7716,12220 7716,11248 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8432" y="11954"><tspan fill="rgb(0,0,0)" stroke="none">2</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(237,28,36)" stroke="none" d="M 9499,11248 L 11282,11248 11282,12220 9499,12220 9499,11248 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="10215" y="11954"><tspan fill="rgb(0,0,0)" stroke="none">3</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(57,10,93)" stroke="none" d="M 11282,11248 L 13065,11248 13065,12220 11282,12220 11282,11248 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="11998" y="11954"><tspan fill="rgb(221,221,221)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(130,106,175)" stroke="none" d="M 13065,11248 L 14843,11248 14843,12220 13065,12220 13065,11248 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="13779" y="11954"><tspan fill="rgb(0,0,0)" stroke="none">4</tspan></tspan></tspan></text>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4149,11248 L 5934,11248"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4149,12220 L 5934,12220"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5932,11248 L 7717,11248"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5932,12220 L 7717,12220"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7715,11248 L 9500,11248"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7715,12220 L 9500,12220"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9498,11248 L 11283,11248"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9498,12220 L 11283,12220"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11281,11248 L 13066,11248"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11281,12220 L 13066,12220"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13064,11248 L 14844,11248"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13064,12220 L 14844,12220"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 4150,11247 L 4150,12221"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5933,11247 L 5933,12221"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 7716,11247 L 7716,12221"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 9499,11247 L 9499,12221"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 11282,11247 L 11282,12221"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 13065,11247 L 13065,12221"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 14843,11247 L 14843,12221"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id33">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11087" y="6799" width="5117" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="11337" y="7500"><tspan fill="rgb(0,0,0)" stroke="none">Split into chunks</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id34">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="4229" y="8423" width="2649" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="4479" y="9124"><tspan fill="rgb(0,0,0)" stroke="none">Chunks</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id35">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11141" y="3197" width="3881" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="11391" y="3898"><tspan fill="rgb(0,0,0)" stroke="none">Source blob</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.LineShape">
|
||||
<g id="id36">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="5063" y="12378" width="3177" height="7114"/>
|
||||
<path fill="none" stroke="rgb(173,197,231)" d="M 5064,12379 L 8064,19098"/>
|
||||
<path fill="rgb(173,197,231)" stroke="none" d="M 8239,19491 L 8193,19019 7919,19141 8239,19491 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.LineShape">
|
||||
<g id="id37">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="5977" y="12124" width="867" height="7368"/>
|
||||
<path fill="none" stroke="rgb(237,28,36)" d="M 6842,12125 L 6124,19063"/>
|
||||
<path fill="rgb(237,28,36)" stroke="none" d="M 6080,19491 L 6276,19059 5977,19028 6080,19491 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.LineShape">
|
||||
<g id="id38">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="4809" y="12251" width="3686" height="7241"/>
|
||||
<path fill="none" stroke="rgb(0,166,93)" d="M 8493,12252 L 5005,19108"/>
|
||||
<path fill="rgb(0,166,93)" stroke="none" d="M 4810,19491 L 5148,19158 4880,19022 4810,19491 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.LineShape">
|
||||
<g id="id39">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="6206" y="12378" width="4194" height="7114"/>
|
||||
<path fill="none" stroke="rgb(237,28,36)" d="M 10398,12379 L 6425,19121"/>
|
||||
<path fill="rgb(237,28,36)" stroke="none" d="M 6207,19491 L 6565,19179 6306,19027 6207,19491 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.LineShape">
|
||||
<g id="id40">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="3667" y="12251" width="8511" height="7241"/>
|
||||
<path fill="none" stroke="rgb(57,10,93)" d="M 12176,12252 L 3995,19212"/>
|
||||
<path fill="rgb(57,10,93)" stroke="none" d="M 3667,19491 L 4107,19314 3913,19085 3667,19491 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.LineShape">
|
||||
<g id="id41">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="6969" y="12378" width="6987" height="7114"/>
|
||||
<path fill="none" stroke="rgb(130,106,175)" d="M 13954,12379 L 7270,19184"/>
|
||||
<path fill="rgb(130,106,175)" stroke="none" d="M 6969,19491 L 7391,19275 7177,19065 6969,19491 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id42">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="3648" y="12614" width="11349" height="1674"/>
|
||||
<path fill="rgb(238,238,238)" stroke="none" d="M 9322,14287 L 3648,14287 3648,12614 14996,12614 14996,14287 9322,14287 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="3898" y="13315"><tspan fill="rgb(0,0,0)" stroke="none">Content-addressed chunks are synced</tspan></tspan><tspan class="TextPosition" x="3898" y="14026"><tspan fill="rgb(0,0,0)" stroke="none">to nodes in the bin they fall into</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id43">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="10344" y="19880" width="8129" height="3096"/>
|
||||
<path fill="rgb(238,238,238)" stroke="none" d="M 14408,22975 L 10344,22975 10344,19880 18472,19880 18472,22975 14408,22975 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="10594" y="20581"><tspan fill="rgb(0,0,0)" stroke="none">Nodes upon receiving </tspan></tspan><tspan class="TextPosition" x="10594" y="21292"><tspan fill="rgb(0,0,0)" stroke="none">chunks sync them to</tspan></tspan><tspan class="TextPosition" x="10594" y="22003"><tspan fill="rgb(0,0,0)" stroke="none">other nodes in the same </tspan></tspan><tspan class="TextPosition" x="10594" y="22714"><tspan fill="rgb(0,0,0)" stroke="none">address space</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id44">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="15421" y="5502" width="4059" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="15671" y="6203"><tspan fill="rgb(0,0,0)" stroke="none">Swarm node</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id45">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="5236" y="600" width="9770" height="1196"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="847px" font-weight="700"><tspan class="TextPosition" x="5486" y="1491"><tspan fill="rgb(0,0,0)" stroke="none">Swarm upload process</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 88 KiB |
1466
docs/swarm-guide/contents/img/high-level-components.svg
Normal file
After Width: | Height: | Size: 147 KiB |
334
docs/swarm-guide/contents/img/kademlia.svg
Normal file
After Width: | Height: | Size: 201 KiB |
345
docs/swarm-guide/contents/img/storage-layer.svg
Normal file
After Width: | Height: | Size: 79 KiB |
BIN
docs/swarm-guide/contents/img/swarm-inside.png
Normal file
After Width: | Height: | Size: 67 KiB |
983
docs/swarm-guide/contents/img/swarm-intro.svg
Normal file
After Width: | Height: | Size: 718 KiB |
BIN
docs/swarm-guide/contents/img/swarm-logo.jpg
Normal file
After Width: | Height: | Size: 230 KiB |
BIN
docs/swarm-guide/contents/img/swarm.png
Normal file
After Width: | Height: | Size: 134 KiB |
2463
docs/swarm-guide/contents/img/syncing-high-level.svg
Normal file
After Width: | Height: | Size: 3.4 MiB |
464
docs/swarm-guide/contents/img/topology.svg
Normal file
@ -0,0 +1,464 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.2" width="215.9mm" height="279.4mm" viewBox="0 0 21590 27940" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
|
||||
<defs class="ClipPathGroup">
|
||||
<clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="21590" height="27940"/>
|
||||
</clipPath>
|
||||
<clipPath id="presentation_clip_path_shrink" clipPathUnits="userSpaceOnUse">
|
||||
<rect x="21" y="27" width="21547" height="27885"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<defs>
|
||||
<font id="EmbeddedFont_1" horiz-adv-x="2048">
|
||||
<font-face font-family="Liberation Sans embedded" units-per-em="2048" font-weight="normal" font-style="normal" ascent="1859" descent="433"/>
|
||||
<missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
|
||||
<glyph unicode="y" horiz-adv-x="1032" d="M 191,-425 C 142,-425 100,-421 67,-414 L 67,-279 C 92,-283 120,-285 151,-285 263,-285 352,-203 417,-38 L 434,5 5,1082 197,1082 425,484 C 428,475 432,464 437,451 442,438 457,394 482,320 507,246 521,205 523,196 L 593,393 830,1082 1020,1082 604,0 C 559,-115 518,-201 479,-258 440,-314 398,-356 351,-384 304,-411 250,-425 191,-425 Z"/>
|
||||
<glyph unicode="w" horiz-adv-x="1501" d="M 1174,0 L 965,0 776,765 740,934 C 734,904 725,861 712,805 699,748 631,480 508,0 L 300,0 -3,1082 175,1082 358,347 C 363,331 377,265 401,149 L 418,223 644,1082 837,1082 1026,339 1072,149 1103,288 1308,1082 1484,1082 1174,0 Z"/>
|
||||
<glyph unicode="v" horiz-adv-x="1023" d="M 613,0 L 400,0 7,1082 199,1082 437,378 C 446,351 469,272 506,141 L 541,258 580,376 826,1082 1017,1082 613,0 Z"/>
|
||||
<glyph unicode="u" horiz-adv-x="884" d="M 314,1082 L 314,396 C 314,325 321,269 335,230 349,191 371,162 402,145 433,128 478,119 537,119 624,119 692,149 742,208 792,267 817,350 817,455 L 817,1082 997,1082 997,231 C 997,105 999,28 1003,0 L 833,0 C 832,3 832,12 831,27 830,42 830,59 829,78 828,97 826,132 825,185 L 822,185 C 781,110 733,58 679,27 624,-4 557,-20 476,-20 357,-20 271,10 216,69 161,128 133,225 133,361 L 133,1082 314,1082 Z"/>
|
||||
<glyph unicode="t" horiz-adv-x="534" d="M 554,8 C 495,-8 434,-16 372,-16 228,-16 156,66 156,229 L 156,951 31,951 31,1082 163,1082 216,1324 336,1324 336,1082 536,1082 536,951 336,951 336,268 C 336,216 345,180 362,159 379,138 408,127 450,127 474,127 509,132 554,141 L 554,8 Z"/>
|
||||
<glyph unicode="s" horiz-adv-x="903" d="M 950,299 C 950,197 912,118 835,63 758,8 650,-20 511,-20 376,-20 273,2 200,47 127,91 79,160 57,254 L 216,285 C 231,227 263,185 311,158 359,131 426,117 511,117 602,117 669,131 712,159 754,187 775,229 775,285 775,328 760,362 731,389 702,416 654,438 589,455 L 460,489 C 357,516 283,542 240,568 196,593 162,624 137,661 112,698 100,743 100,796 100,895 135,970 206,1022 276,1073 378,1099 513,1099 632,1099 727,1078 798,1036 868,994 912,927 931,834 L 769,814 C 759,862 732,899 689,925 645,950 586,963 513,963 432,963 372,951 333,926 294,901 275,864 275,814 275,783 283,758 299,738 315,718 339,701 370,687 401,673 467,654 568,629 663,605 732,583 774,563 816,542 849,520 874,495 898,470 917,442 930,410 943,377 950,340 950,299 Z"/>
|
||||
<glyph unicode="r" horiz-adv-x="525" d="M 142,0 L 142,830 C 142,906 140,990 136,1082 L 306,1082 C 311,959 314,886 314,861 L 318,861 C 347,954 380,1017 417,1051 454,1085 507,1102 575,1102 599,1102 623,1099 648,1092 L 648,927 C 624,934 592,937 552,937 477,937 420,905 381,841 342,776 322,684 322,564 L 322,0 142,0 Z"/>
|
||||
<glyph unicode="p" horiz-adv-x="930" d="M 1053,546 C 1053,169 920,-20 655,-20 488,-20 376,43 319,168 L 314,168 C 317,163 318,106 318,-2 L 318,-425 138,-425 138,861 C 138,972 136,1046 132,1082 L 306,1082 C 307,1079 308,1070 309,1054 310,1037 312,1012 314,978 315,944 316,921 316,908 L 320,908 C 352,975 394,1024 447,1055 500,1086 569,1101 655,1101 788,1101 888,1056 954,967 1020,878 1053,737 1053,546 Z M 864,542 C 864,693 844,800 803,865 762,930 698,962 609,962 538,962 482,947 442,917 401,887 371,840 350,777 329,713 318,630 318,528 318,386 341,281 386,214 431,147 505,113 607,113 696,113 762,146 803,212 844,277 864,387 864,542 Z"/>
|
||||
<glyph unicode="o" horiz-adv-x="976" d="M 1053,542 C 1053,353 1011,212 928,119 845,26 724,-20 565,-20 407,-20 288,28 207,125 126,221 86,360 86,542 86,915 248,1102 571,1102 736,1102 858,1057 936,966 1014,875 1053,733 1053,542 Z M 864,542 C 864,691 842,800 798,868 753,935 679,969 574,969 469,969 393,935 346,866 299,797 275,689 275,542 275,399 298,292 345,221 391,149 464,113 563,113 671,113 748,148 795,217 841,286 864,395 864,542 Z"/>
|
||||
<glyph unicode="n" horiz-adv-x="884" d="M 825,0 L 825,686 C 825,757 818,813 804,852 790,891 768,920 737,937 706,954 661,963 602,963 515,963 447,933 397,874 347,815 322,732 322,627 L 322,0 142,0 142,851 C 142,977 140,1054 136,1082 L 306,1082 C 307,1079 307,1070 308,1055 309,1040 310,1024 311,1005 312,986 313,950 314,897 L 317,897 C 358,972 406,1025 461,1056 515,1087 582,1102 663,1102 782,1102 869,1073 924,1014 979,955 1006,857 1006,721 L 1006,0 825,0 Z"/>
|
||||
<glyph unicode="m" horiz-adv-x="1455" d="M 768,0 L 768,686 C 768,791 754,863 725,903 696,943 645,963 570,963 493,963 433,934 388,875 343,816 321,734 321,627 L 321,0 142,0 142,851 C 142,977 140,1054 136,1082 L 306,1082 C 307,1079 307,1070 308,1055 309,1040 310,1024 311,1005 312,986 313,950 314,897 L 317,897 C 356,974 400,1027 450,1057 500,1087 561,1102 633,1102 715,1102 780,1086 828,1053 875,1020 908,968 927,897 L 930,897 C 967,970 1013,1022 1066,1054 1119,1086 1183,1102 1258,1102 1367,1102 1447,1072 1497,1013 1546,954 1571,856 1571,721 L 1571,0 1393,0 1393,686 C 1393,791 1379,863 1350,903 1321,943 1270,963 1195,963 1116,963 1055,934 1012,876 968,817 946,734 946,627 L 946,0 768,0 Z"/>
|
||||
<glyph unicode="l" horiz-adv-x="185" d="M 138,0 L 138,1484 318,1484 318,0 138,0 Z"/>
|
||||
<glyph unicode="k" horiz-adv-x="894" d="M 816,0 L 450,494 318,385 318,0 138,0 138,1484 318,1484 318,557 793,1082 1004,1082 565,617 1027,0 816,0 Z"/>
|
||||
<glyph unicode="i" horiz-adv-x="194" d="M 137,1312 L 137,1484 317,1484 317,1312 137,1312 Z M 137,0 L 137,1082 317,1082 317,0 137,0 Z"/>
|
||||
<glyph unicode="h" horiz-adv-x="875" d="M 317,897 C 356,968 402,1020 457,1053 511,1086 580,1102 663,1102 780,1102 867,1073 923,1015 978,956 1006,858 1006,721 L 1006,0 825,0 825,686 C 825,762 818,819 804,856 790,893 767,920 735,937 703,954 659,963 602,963 517,963 450,934 399,875 348,816 322,737 322,638 L 322,0 142,0 142,1484 322,1484 322,1098 C 322,1057 321,1015 319,972 316,929 315,904 314,897 L 317,897 Z"/>
|
||||
<glyph unicode="g" horiz-adv-x="930" d="M 548,-425 C 430,-425 336,-402 266,-356 196,-309 151,-243 131,-158 L 312,-132 C 324,-182 351,-220 392,-248 433,-274 486,-288 553,-288 732,-288 822,-183 822,27 L 822,201 820,201 C 786,132 739,80 680,45 621,10 551,-8 472,-8 339,-8 242,36 180,124 117,212 86,350 86,539 86,730 120,872 187,963 254,1054 355,1099 492,1099 569,1099 635,1082 692,1047 748,1012 791,962 822,897 L 824,897 C 824,917 825,952 828,1001 831,1050 833,1077 836,1082 L 1007,1082 C 1003,1046 1001,971 1001,858 L 1001,31 C 1001,-273 850,-425 548,-425 Z M 822,541 C 822,629 810,705 786,769 762,832 728,881 685,915 641,948 591,965 536,965 444,965 377,932 335,865 293,798 272,690 272,541 272,393 292,287 331,222 370,157 438,125 533,125 590,125 640,142 684,175 728,208 762,256 786,319 810,381 822,455 822,541 Z"/>
|
||||
<glyph unicode="f" horiz-adv-x="553" d="M 361,951 L 361,0 181,0 181,951 29,951 29,1082 181,1082 181,1204 C 181,1303 203,1374 246,1417 289,1460 356,1482 445,1482 495,1482 537,1478 572,1470 L 572,1333 C 542,1338 515,1341 492,1341 446,1341 413,1329 392,1306 371,1283 361,1240 361,1179 L 361,1082 572,1082 572,951 361,951 Z"/>
|
||||
<glyph unicode="e" horiz-adv-x="976" d="M 276,503 C 276,379 302,283 353,216 404,149 479,115 578,115 656,115 719,131 766,162 813,193 844,233 861,281 L 1019,236 C 954,65 807,-20 578,-20 418,-20 296,28 213,123 129,218 87,360 87,548 87,727 129,864 213,959 296,1054 416,1102 571,1102 889,1102 1048,910 1048,527 L 1048,503 276,503 Z M 862,641 C 852,755 823,838 775,891 727,943 658,969 568,969 481,969 412,940 361,882 310,823 282,743 278,641 L 862,641 Z"/>
|
||||
<glyph unicode="d" horiz-adv-x="930" d="M 821,174 C 788,105 744,55 689,25 634,-5 565,-20 484,-20 347,-20 247,26 183,118 118,210 86,349 86,536 86,913 219,1102 484,1102 566,1102 634,1087 689,1057 744,1027 788,979 821,914 L 823,914 821,1035 821,1484 1001,1484 1001,223 C 1001,110 1003,36 1007,0 L 835,0 C 833,11 831,35 829,74 826,113 825,146 825,174 L 821,174 Z M 275,542 C 275,391 295,282 335,217 375,152 440,119 530,119 632,119 706,154 752,225 798,296 821,405 821,554 821,697 798,802 752,869 706,936 633,969 532,969 441,969 376,936 336,869 295,802 275,693 275,542 Z"/>
|
||||
<glyph unicode="c" horiz-adv-x="894" d="M 275,546 C 275,402 298,295 343,226 388,157 457,122 548,122 612,122 666,139 709,174 752,209 778,262 788,334 L 970,322 C 956,218 912,135 837,73 762,11 668,-20 553,-20 402,-20 286,28 207,124 127,219 87,359 87,542 87,724 127,863 207,959 287,1054 402,1102 551,1102 662,1102 754,1073 827,1016 900,959 945,880 964,779 L 779,765 C 770,825 746,873 708,908 670,943 616,961 546,961 451,961 382,929 339,866 296,803 275,696 275,546 Z"/>
|
||||
<glyph unicode="b" horiz-adv-x="930" d="M 1053,546 C 1053,169 920,-20 655,-20 573,-20 505,-5 451,25 396,54 352,102 318,168 L 316,168 C 316,147 315,116 312,74 309,31 307,7 306,0 L 132,0 C 136,36 138,110 138,223 L 138,1484 318,1484 318,1061 C 318,1018 317,967 314,908 L 318,908 C 351,977 396,1027 451,1057 506,1087 574,1102 655,1102 792,1102 892,1056 957,964 1021,872 1053,733 1053,546 Z M 864,540 C 864,691 844,800 804,865 764,930 699,963 609,963 508,963 434,928 388,859 341,790 318,680 318,529 318,387 341,282 386,215 431,147 505,113 607,113 698,113 763,147 804,214 844,281 864,389 864,540 Z"/>
|
||||
<glyph unicode="a" horiz-adv-x="1068" d="M 414,-20 C 305,-20 224,9 169,66 114,123 87,202 87,302 87,414 124,500 198,560 271,620 390,652 554,656 L 797,660 797,719 C 797,807 778,870 741,908 704,946 645,965 565,965 484,965 426,951 389,924 352,897 330,853 323,793 L 135,810 C 166,1005 310,1102 569,1102 705,1102 807,1071 876,1009 945,946 979,856 979,738 L 979,272 C 979,219 986,179 1000,152 1014,125 1041,111 1080,111 1097,111 1117,113 1139,118 L 1139,6 C 1094,-5 1047,-10 1000,-10 933,-10 885,8 855,43 824,78 807,132 803,207 L 797,207 C 751,124 698,66 637,32 576,-3 501,-20 414,-20 Z M 455,115 C 521,115 580,130 631,160 682,190 723,231 753,284 782,336 797,390 797,445 L 797,534 600,530 C 515,529 451,520 408,504 364,488 330,463 307,430 284,397 272,353 272,299 272,240 288,195 320,163 351,131 396,115 455,115 Z"/>
|
||||
<glyph unicode="X" horiz-adv-x="1289" d="M 1112,0 L 689,616 257,0 46,0 582,732 87,1409 298,1409 690,856 1071,1409 1282,1409 800,739 1323,0 1112,0 Z"/>
|
||||
<glyph unicode="U" horiz-adv-x="1170" d="M 731,-20 C 616,-20 515,1 429,43 343,85 276,146 229,226 182,306 158,401 158,512 L 158,1409 349,1409 349,528 C 349,399 382,302 447,235 512,168 607,135 730,135 857,135 955,170 1026,239 1096,308 1131,408 1131,541 L 1131,1409 1321,1409 1321,530 C 1321,416 1297,318 1249,235 1200,152 1132,89 1044,46 955,2 851,-20 731,-20 Z"/>
|
||||
<glyph unicode="S" horiz-adv-x="1188" d="M 1272,389 C 1272,259 1221,158 1120,87 1018,16 875,-20 690,-20 347,-20 148,99 93,338 L 278,375 C 299,290 345,228 414,189 483,149 578,129 697,129 820,129 916,150 983,193 1050,235 1083,297 1083,379 1083,425 1073,462 1052,491 1031,520 1001,543 963,562 925,581 880,596 827,609 774,622 716,635 652,650 541,675 456,699 399,724 341,749 295,776 262,807 229,837 203,872 186,913 168,954 159,1000 159,1053 159,1174 205,1267 298,1332 390,1397 522,1430 694,1430 854,1430 976,1406 1061,1357 1146,1308 1205,1224 1239,1106 L 1051,1073 C 1030,1148 991,1202 933,1236 875,1269 795,1286 692,1286 579,1286 493,1267 434,1230 375,1193 345,1137 345,1063 345,1020 357,984 380,956 403,927 436,903 479,884 522,864 609,840 738,811 781,801 825,791 868,781 911,770 952,758 991,744 1030,729 1067,712 1102,693 1136,674 1166,650 1191,622 1216,594 1236,561 1251,523 1265,485 1272,440 1272,389 Z"/>
|
||||
<glyph unicode="R" horiz-adv-x="1225" d="M 1164,0 L 798,585 359,585 359,0 168,0 168,1409 831,1409 C 990,1409 1112,1374 1199,1303 1285,1232 1328,1133 1328,1006 1328,901 1298,813 1237,742 1176,671 1091,626 984,607 L 1384,0 1164,0 Z M 1136,1004 C 1136,1086 1108,1149 1053,1192 997,1235 917,1256 812,1256 L 359,1256 359,736 820,736 C 921,736 999,760 1054,807 1109,854 1136,919 1136,1004 Z"/>
|
||||
<glyph unicode="O" horiz-adv-x="1409" d="M 1495,711 C 1495,564 1467,435 1411,324 1354,213 1273,128 1168,69 1063,10 938,-20 795,-20 650,-20 526,9 421,68 316,127 235,212 180,323 125,434 97,563 97,711 97,936 159,1113 282,1240 405,1367 577,1430 797,1430 940,1430 1065,1402 1170,1345 1275,1288 1356,1205 1412,1096 1467,987 1495,859 1495,711 Z M 1300,711 C 1300,886 1256,1024 1169,1124 1081,1224 957,1274 797,1274 636,1274 511,1225 423,1126 335,1027 291,889 291,711 291,534 336,394 425,291 514,187 637,135 795,135 958,135 1083,185 1170,286 1257,386 1300,528 1300,711 Z"/>
|
||||
<glyph unicode="N" horiz-adv-x="1151" d="M 1082,0 L 328,1200 333,1103 338,936 338,0 168,0 168,1409 390,1409 1152,201 C 1144,332 1140,426 1140,485 L 1140,1409 1312,1409 1312,0 1082,0 Z"/>
|
||||
<glyph unicode="D" horiz-adv-x="1225" d="M 1381,719 C 1381,574 1353,447 1296,338 1239,229 1159,145 1055,87 951,29 831,0 695,0 L 168,0 168,1409 634,1409 C 873,1409 1057,1349 1187,1230 1316,1110 1381,940 1381,719 Z M 1189,719 C 1189,894 1141,1027 1046,1119 950,1210 811,1256 630,1256 L 359,1256 359,153 673,153 C 776,153 867,176 946,221 1024,266 1084,332 1126,417 1168,502 1189,603 1189,719 Z"/>
|
||||
<glyph unicode="9" horiz-adv-x="958" d="M 1042,733 C 1042,491 998,305 910,175 821,45 695,-20 532,-20 422,-20 334,3 268,50 201,96 154,171 125,274 L 297,301 C 333,184 412,125 535,125 638,125 718,173 775,269 832,365 861,502 864,680 837,620 792,572 727,536 662,499 591,481 514,481 387,481 286,524 210,611 134,698 96,813 96,956 96,1103 137,1219 220,1304 303,1388 418,1430 565,1430 722,1430 840,1372 921,1256 1002,1140 1042,966 1042,733 Z M 846,907 C 846,1020 820,1112 768,1181 716,1250 646,1284 559,1284 472,1284 404,1255 354,1196 304,1137 279,1057 279,956 279,853 304,772 354,713 404,653 472,623 557,623 609,623 657,635 702,659 747,682 782,716 808,759 833,802 846,852 846,907 Z"/>
|
||||
<glyph unicode="8" horiz-adv-x="976" d="M 1050,393 C 1050,263 1009,162 926,89 843,16 725,-20 570,-20 419,-20 302,16 217,87 132,158 89,260 89,391 89,483 115,560 168,623 221,686 288,724 370,737 L 370,741 C 293,759 233,798 189,858 144,918 122,988 122,1069 122,1176 162,1263 243,1330 323,1397 431,1430 566,1430 705,1430 814,1397 895,1332 975,1267 1015,1178 1015,1067 1015,986 993,916 948,856 903,796 842,758 765,743 L 765,739 C 855,724 925,686 975,625 1025,563 1050,486 1050,393 Z M 828,1057 C 828,1216 741,1296 566,1296 481,1296 417,1276 373,1236 328,1196 306,1136 306,1057 306,976 329,915 375,873 420,830 485,809 568,809 653,809 717,829 762,868 806,907 828,970 828,1057 Z M 863,410 C 863,497 837,563 785,608 733,652 660,674 566,674 475,674 403,650 352,603 301,555 275,489 275,406 275,212 374,115 572,115 670,115 743,139 791,186 839,233 863,307 863,410 Z"/>
|
||||
<glyph unicode="6" horiz-adv-x="958" d="M 1049,461 C 1049,312 1009,195 928,109 847,23 736,-20 594,-20 435,-20 314,39 230,157 146,275 104,447 104,672 104,916 148,1103 235,1234 322,1365 447,1430 608,1430 821,1430 955,1334 1010,1143 L 838,1112 C 803,1227 725,1284 606,1284 503,1284 424,1236 368,1141 311,1045 283,906 283,725 316,786 362,832 421,864 480,895 548,911 625,911 755,911 858,870 935,789 1011,708 1049,598 1049,461 Z M 866,453 C 866,555 841,634 791,689 741,744 671,772 582,772 498,772 430,748 379,699 327,650 301,582 301,496 301,387 328,298 382,229 435,160 504,125 588,125 675,125 743,154 792,213 841,271 866,351 866,453 Z"/>
|
||||
<glyph unicode="5" horiz-adv-x="985" d="M 1053,459 C 1053,310 1009,193 921,108 832,23 710,-20 553,-20 422,-20 316,9 235,66 154,123 103,206 82,315 L 264,336 C 302,197 400,127 557,127 654,127 729,156 784,215 839,273 866,353 866,455 866,544 839,615 784,670 729,725 654,752 561,752 512,752 467,744 425,729 383,714 341,688 299,651 L 123,651 170,1409 971,1409 971,1256 334,1256 307,809 C 385,869 482,899 598,899 737,899 847,858 930,777 1012,696 1053,590 1053,459 Z"/>
|
||||
<glyph unicode="4" horiz-adv-x="1041" d="M 881,319 L 881,0 711,0 711,319 47,319 47,459 692,1409 881,1409 881,461 1079,461 1079,319 881,319 Z M 711,1206 C 710,1202 700,1184 683,1153 666,1122 653,1100 644,1087 L 283,555 229,481 213,461 711,461 711,1206 Z"/>
|
||||
<glyph unicode="3" horiz-adv-x="985" d="M 1049,389 C 1049,259 1008,158 925,87 842,16 724,-20 571,-20 428,-20 315,12 230,77 145,141 94,236 78,362 L 264,379 C 288,212 390,129 571,129 662,129 733,151 785,196 836,241 862,307 862,395 862,472 833,532 774,575 715,618 629,639 518,639 L 416,639 416,795 514,795 C 613,795 689,817 744,860 798,903 825,962 825,1038 825,1113 803,1173 759,1217 714,1260 648,1282 561,1282 482,1282 418,1262 369,1221 320,1180 291,1123 283,1049 L 102,1063 C 115,1178 163,1268 246,1333 328,1398 434,1430 563,1430 704,1430 814,1397 893,1332 971,1266 1010,1174 1010,1057 1010,967 985,894 935,838 884,781 811,743 715,723 L 715,719 C 820,708 902,672 961,613 1020,554 1049,479 1049,389 Z"/>
|
||||
<glyph unicode="2" horiz-adv-x="940" d="M 103,0 L 103,127 C 137,205 179,274 228,334 277,393 328,447 382,496 436,544 490,589 543,630 596,671 643,713 686,754 729,795 763,839 790,884 816,929 829,981 829,1038 829,1115 806,1175 761,1218 716,1261 653,1282 572,1282 495,1282 432,1261 383,1220 333,1178 304,1119 295,1044 L 111,1061 C 124,1174 172,1263 255,1330 337,1397 443,1430 572,1430 714,1430 823,1397 900,1330 976,1263 1014,1167 1014,1044 1014,989 1002,935 977,881 952,827 914,773 865,719 816,665 721,581 582,468 505,405 444,349 399,299 354,248 321,200 301,153 L 1036,153 1036,0 103,0 Z"/>
|
||||
<glyph unicode="1" horiz-adv-x="903" d="M 156,0 L 156,153 515,153 515,1237 197,1010 197,1180 530,1409 696,1409 696,153 1039,153 1039,0 156,0 Z"/>
|
||||
<glyph unicode="0" horiz-adv-x="995" d="M 1059,705 C 1059,470 1018,290 935,166 852,42 729,-20 567,-20 405,-20 283,42 202,165 121,288 80,468 80,705 80,947 120,1128 199,1249 278,1370 402,1430 573,1430 739,1430 862,1369 941,1247 1020,1125 1059,944 1059,705 Z M 876,705 C 876,908 853,1056 806,1147 759,1238 681,1284 573,1284 462,1284 383,1239 335,1149 286,1059 262,911 262,705 262,505 287,359 336,266 385,173 462,127 569,127 675,127 753,174 802,269 851,364 876,509 876,705 Z"/>
|
||||
<glyph unicode="," horiz-adv-x="204" d="M 385,219 L 385,51 C 385,-20 379,-79 366,-126 353,-173 334,-219 307,-262 L 184,-262 C 247,-171 278,-84 278,0 L 190,0 190,219 385,219 Z"/>
|
||||
<glyph unicode=" " horiz-adv-x="571"/>
|
||||
</font>
|
||||
</defs>
|
||||
<defs class="TextShapeIndex">
|
||||
<g ooo:slide="id1" ooo:id-list="id3 id4 id5 id6 id7 id8 id9 id10 id11 id12 id13 id14 id15 id16 id17 id18 id19 id20 id21 id22 id23 id24 id25 id26 id27 id28 id29 id30 id31 id32"/>
|
||||
</defs>
|
||||
<defs class="EmbeddedBulletChars">
|
||||
<g id="bullet-char-template-57356" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-57354" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-10146" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-10132" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-10007" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-10004" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-9679" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-8226" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-8211" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z"/>
|
||||
</g>
|
||||
<g id="bullet-char-template-61548" transform="scale(0.00048828125,-0.00048828125)">
|
||||
<path d="M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"/>
|
||||
</g>
|
||||
</defs>
|
||||
<defs class="TextEmbeddedBitmaps"/>
|
||||
<g>
|
||||
<g id="id2" class="Master_Slide">
|
||||
<g id="bg-id2" class="Background"/>
|
||||
<g id="bo-id2" class="BackgroundObjects"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="SlideGroup">
|
||||
<g>
|
||||
<g id="container-id1">
|
||||
<g id="id1" class="Slide" clip-path="url(#presentation_clip_path)">
|
||||
<g class="Page">
|
||||
<g class="com.sun.star.drawing.TableShape">
|
||||
<g>
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="1365" y="11106" width="18518" height="4161"/>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 1367,11108 L 3679,11108 3679,13107 1367,13107 1367,11108 Z"/>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 3679,11108 L 5991,11108 5991,13107 3679,13107 3679,11108 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="4243" y="12256"><tspan fill="rgb(0,0,0)" stroke="none">01001</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 5991,11108 L 8303,11108 8303,13107 5991,13107 5991,11108 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="6555" y="12256"><tspan fill="rgb(0,0,0)" stroke="none">10101</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 8303,11108 L 10615,11108 10615,13107 8303,13107 8303,11108 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="8867" y="12256"><tspan fill="rgb(0,0,0)" stroke="none">00010</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 10615,11108 L 12927,11108 12927,13107 10615,13107 10615,11108 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="11179" y="12256"><tspan fill="rgb(0,0,0)" stroke="none">10001</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 12927,11108 L 15239,11108 15239,13107 12927,13107 12927,11108 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="13524" y="12256"><tspan fill="rgb(0,0,0)" stroke="none">00111</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 15239,11108 L 17551,11108 17551,13107 15239,13107 15239,11108 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="15836" y="12256"><tspan fill="rgb(0,0,0)" stroke="none">11100</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 17551,11108 L 19880,11108 19880,13107 17551,13107 17551,11108 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="18157" y="12256"><tspan fill="rgb(0,0,0)" stroke="none">01110</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(204,204,204)" stroke="none" d="M 1367,13107 L 3679,13107 3679,15264 1367,15264 1367,13107 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="2064" y="13623"><tspan fill="rgb(0,0,0)" stroke="none">XOR </tspan></tspan><tspan class="TextPosition" x="1732" y="14097"><tspan fill="rgb(0,0,0)" stroke="none">distance</tspan></tspan><tspan class="TextPosition" x="2345" y="14571"><tspan fill="rgb(0,0,0)" stroke="none">to</tspan></tspan><tspan class="TextPosition" x="1948" y="15045"><tspan fill="rgb(0,0,0)" stroke="none">00110</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(204,204,204)" stroke="none" d="M 3679,13107 L 5991,13107 5991,15264 3679,15264 3679,13107 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="4598" y="14334"><tspan fill="rgb(0,0,0)" stroke="none">15</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(204,204,204)" stroke="none" d="M 5991,13107 L 8303,13107 8303,15264 5991,15264 5991,13107 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="6910" y="14334"><tspan fill="rgb(0,0,0)" stroke="none">19</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(204,204,204)" stroke="none" d="M 8303,13107 L 10615,13107 10615,15264 8303,15264 8303,13107 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="9341" y="14334"><tspan fill="rgb(0,0,0)" stroke="none">4</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(204,204,204)" stroke="none" d="M 10615,13107 L 12927,13107 12927,15264 10615,15264 10615,13107 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="11534" y="14334"><tspan fill="rgb(0,0,0)" stroke="none">23</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(204,204,204)" stroke="none" d="M 12927,13107 L 15239,13107 15239,15264 12927,15264 12927,13107 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="13965" y="14334"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(204,204,204)" stroke="none" d="M 15239,13107 L 17551,13107 17551,15264 15239,15264 15239,13107 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="16158" y="14334"><tspan fill="rgb(0,0,0)" stroke="none">26</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(204,204,204)" stroke="none" d="M 17551,13107 L 19880,13107 19880,15264 17551,15264 17551,13107 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="18597" y="14334"><tspan fill="rgb(0,0,0)" stroke="none">8</tspan></tspan></tspan></text>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 1366,11108 L 19881,11108"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 1366,13107 L 3680,13107"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 1366,15264 L 3680,15264"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3678,13107 L 5992,13107"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3678,15264 L 5992,15264"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5990,13107 L 8304,13107"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5990,15264 L 8304,15264"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8302,13107 L 10616,13107"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8302,15264 L 10616,15264"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10614,13107 L 12928,13107"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10614,15264 L 12928,15264"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12926,13107 L 15240,13107"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12926,15264 L 15240,15264"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15238,13107 L 17552,13107"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15238,15264 L 17552,15264"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 17550,13107 L 19881,13107"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 17550,15264 L 19881,15264"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 1367,11107 L 1367,15265"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 3679,11107 L 3679,15265"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 5991,11107 L 5991,15265"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 8303,11107 L 8303,15265"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10615,11107 L 10615,15265"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 12927,11107 L 12927,15265"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15239,11107 L 15239,15265"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 17551,11107 L 17551,13108"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 19880,11107 L 19880,13108"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 17551,13106 L 17551,15265"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 19880,13106 L 19880,15265"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TableShape">
|
||||
<g>
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="6269" y="17729" width="9247" height="6173"/>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 6271,17731 L 10161,17731 10161,18965 6271,18965 6271,17731 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8041" y="18437"><tspan fill="rgb(0,0,0)" stroke="none">0</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(179,179,179)" stroke="none" d="M 10161,17731 L 15513,17731 15513,18965 10161,18965 10161,17731 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="10411" y="18496"><tspan fill="rgb(0,0,0)" stroke="none">10101,10001,11100</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(204,204,204)" stroke="none" d="M 6271,18965 L 10161,18965 10161,20199 6271,20199 6271,18965 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8041" y="19671"><tspan fill="rgb(0,0,0)" stroke="none">1</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(204,204,204)" stroke="none" d="M 10161,18965 L 15513,18965 15513,20199 10161,20199 10161,18965 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="10411" y="19730"><tspan fill="rgb(0,0,0)" stroke="none">01001</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(230,230,230)" stroke="none" d="M 6271,20199 L 10161,20199 10161,21433 6271,21433 6271,20199 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8041" y="20905"><tspan fill="rgb(0,0,0)" stroke="none">2</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(230,230,230)" stroke="none" d="M 10161,20199 L 15513,20199 15513,21433 10161,21433 10161,20199 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="10411" y="20964"><tspan fill="rgb(0,0,0)" stroke="none">01110</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(204,204,204)" stroke="none" d="M 6271,21433 L 10161,21433 10161,22667 6271,22667 6271,21433 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8041" y="22139"><tspan fill="rgb(0,0,0)" stroke="none">3</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(204,204,204)" stroke="none" d="M 10161,21433 L 15513,21433 15513,22667 10161,22667 10161,21433 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="10411" y="22198"><tspan fill="rgb(0,0,0)" stroke="none">00010</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(230,230,230)" stroke="none" d="M 6271,22667 L 10161,22667 10161,23899 6271,23899 6271,22667 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8041" y="23373"><tspan fill="rgb(0,0,0)" stroke="none">4</tspan></tspan></tspan></text>
|
||||
<path fill="rgb(230,230,230)" stroke="none" d="M 10161,22667 L 15513,22667 15513,23899 10161,23899 10161,22667 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="423px" font-weight="400"><tspan class="TextPosition" x="10411" y="23431"><tspan fill="rgb(0,0,0)" stroke="none">00111</tspan></tspan></tspan></text>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6270,17731 L 15514,17731"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6270,18965 L 15514,18965"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6270,20199 L 15514,20199"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6270,21433 L 15514,21433"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6270,22667 L 10162,22667"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6270,23899 L 10162,23899"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10160,22667 L 15514,22667"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10160,23899 L 15514,23899"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 6271,17730 L 6271,23900"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10161,17730 L 10161,18966"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15513,17730 L 15513,18966"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10161,18964 L 10161,20200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15513,18964 L 15513,20200"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10161,20198 L 10161,21434"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15513,20198 L 15513,21434"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10161,21432 L 10161,22668"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15513,21432 L 15513,22668"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 10161,22666 L 10161,23900"/>
|
||||
<path fill="none" stroke="rgb(255,255,255)" stroke-width="2" stroke-linejoin="round" d="M 15513,22666 L 15513,23900"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.LineShape">
|
||||
<g id="id3">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="5023" y="20184" width="12275" height="83"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" stroke-width="81" stroke-linejoin="round" d="M 5064,20225 L 17256,20225"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id4">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="17383" y="19417" width="3390" height="1674"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="17633" y="20118"><tspan fill="rgb(0,0,0)" stroke="none">Saturation</tspan></tspan><tspan class="TextPosition" x="17633" y="20829"><tspan fill="rgb(0,0,0)" stroke="none">Depth</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id5">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="7491" y="16088" width="6523" height="1674"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="7741" y="16789"><tspan fill="rgb(0,0,0)" stroke="none">Node 00110 has</tspan></tspan><tspan class="TextPosition" x="7741" y="17500"><tspan fill="rgb(0,0,0)" stroke="none">kademlia connectivity</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id6">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="10061" y="5441" width="1109" height="1061"/>
|
||||
<path fill="rgb(245,130,32)" stroke="none" d="M 10614,5442 C 10927,5442 11167,5671 11167,5970 11167,6269 10927,6499 10614,6499 10301,6499 10062,6269 10062,5970 10062,5671 10301,5442 10614,5442 Z M 10062,5442 L 10062,5442 Z M 11168,6500 L 11168,6500 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10614,5442 C 10927,5442 11167,5671 11167,5970 11167,6269 10927,6499 10614,6499 10301,6499 10062,6269 10062,5970 10062,5671 10301,5442 10614,5442 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10062,5442 L 10062,5442 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 11168,6500 L 11168,6500 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id7">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="4450" y="2871" width="1109" height="1062"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 5003,2872 C 5316,2872 5556,3101 5556,3401 5556,3701 5316,3930 5003,3930 4690,3930 4451,3701 4451,3401 4451,3101 4690,2872 5003,2872 Z M 4451,2872 L 4451,2872 Z M 5557,3931 L 5557,3931 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 5003,2872 C 5316,2872 5556,3101 5556,3401 5556,3701 5316,3930 5003,3930 4690,3930 4451,3701 4451,3401 4451,3101 4690,2872 5003,2872 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4451,2872 L 4451,2872 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 5557,3931 L 5557,3931 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id8">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9371" y="1714" width="1109" height="1061"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 9924,1715 C 10237,1715 10477,1944 10477,2243 10477,2542 10237,2772 9924,2772 9611,2772 9372,2542 9372,2243 9372,1944 9611,1715 9924,1715 Z M 9372,1715 L 9372,1715 Z M 10478,2773 L 10478,2773 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9924,1715 C 10237,1715 10477,1944 10477,2243 10477,2542 10237,2772 9924,2772 9611,2772 9372,2542 9372,2243 9372,1944 9611,1715 9924,1715 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 9372,1715 L 9372,1715 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 10478,2773 L 10478,2773 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id9">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="14894" y="3599" width="1109" height="1061"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 15447,3600 C 15760,3600 16000,3829 16000,4128 16000,4427 15760,4657 15447,4657 15134,4657 14895,4427 14895,4128 14895,3829 15134,3600 15447,3600 Z M 14895,3600 L 14895,3600 Z M 16001,4658 L 16001,4658 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 15447,3600 C 15760,3600 16000,3829 16000,4128 16000,4427 15760,4657 15447,4657 15134,4657 14895,4427 14895,4128 14895,3829 15134,3600 15447,3600 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 14895,3600 L 14895,3600 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 16001,4658 L 16001,4658 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id10">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="15396" y="6685" width="1109" height="1061"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 15949,6686 C 16262,6686 16502,6915 16502,7214 16502,7513 16262,7743 15949,7743 15636,7743 15397,7513 15397,7214 15397,6915 15636,6686 15949,6686 Z M 15397,6686 L 15397,6686 Z M 16503,7744 L 16503,7744 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 15949,6686 C 16262,6686 16502,6915 16502,7214 16502,7513 16262,7743 15949,7743 15636,7743 15397,7513 15397,7214 15397,6915 15636,6686 15949,6686 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 15397,6686 L 15397,6686 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 16503,7744 L 16503,7744 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id11">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="12183" y="9509" width="1109" height="1062"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 12736,9510 C 13049,9510 13289,9739 13289,10039 13289,10339 13049,10568 12736,10568 12423,10568 12184,10339 12184,10039 12184,9739 12423,9510 12736,9510 Z M 12184,9510 L 12184,9510 Z M 13290,10569 L 13290,10569 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 12736,9510 C 13049,9510 13289,9739 13289,10039 13289,10339 13049,10568 12736,10568 12423,10568 12184,10339 12184,10039 12184,9739 12423,9510 12736,9510 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 12184,9510 L 12184,9510 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 13290,10569 L 13290,10569 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id12">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="7463" y="9720" width="1109" height="1061"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 8016,9721 C 8329,9721 8569,9950 8569,10249 8569,10548 8329,10778 8016,10778 7703,10778 7464,10548 7464,10249 7464,9950 7703,9721 8016,9721 Z M 7464,9721 L 7464,9721 Z M 8570,10779 L 8570,10779 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8016,9721 C 8329,9721 8569,9950 8569,10249 8569,10548 8329,10778 8016,10778 7703,10778 7464,10548 7464,10249 7464,9950 7703,9721 8016,9721 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 7464,9721 L 7464,9721 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 8570,10779 L 8570,10779 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id13">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="3948" y="6926" width="1109" height="1061"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 4501,6927 C 4814,6927 5054,7156 5054,7455 5054,7754 4814,7984 4501,7984 4188,7984 3949,7754 3949,7455 3949,7156 4188,6927 4501,6927 Z M 3949,6927 L 3949,6927 Z M 5055,7985 L 5055,7985 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 4501,6927 C 4814,6927 5054,7156 5054,7455 5054,7754 4814,7984 4501,7984 4188,7984 3949,7754 3949,7455 3949,7156 4188,6927 4501,6927 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 3949,6927 L 3949,6927 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 5055,7985 L 5055,7985 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id14">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9030" y="969" width="2260" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="9280" y="1670"><tspan fill="rgb(0,0,0)" stroke="none">01001</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id15">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11234" y="5245" width="2213" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="11484" y="5946"><tspan fill="rgb(0,0,0)" stroke="none">00110</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id16">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="14595" y="2876" width="2260" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="14845" y="3577"><tspan fill="rgb(0,0,0)" stroke="none">10101</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id17">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="14997" y="5879" width="2260" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="15247" y="6580"><tspan fill="rgb(0,0,0)" stroke="none">00010</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id18">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="13086" y="8946" width="2260" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="13336" y="9647"><tspan fill="rgb(0,0,0)" stroke="none">10001</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id19">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="7164" y="8847" width="2167" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="7414" y="9548"><tspan fill="rgb(0,0,0)" stroke="none">00111</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id20">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="3448" y="5992" width="2167" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="3698" y="6693"><tspan fill="rgb(0,0,0)" stroke="none">11100</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id21">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="4187" y="2026" width="2167" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="4437" y="2727"><tspan fill="rgb(0,0,0)" stroke="none">01110</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id22">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11007" y="4130" width="3890" height="1468"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 11409,5445 L 14494,4282"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 11007,5597 L 11481,5579 11375,5298 11007,5597 Z"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 14896,4130 L 14422,4148 14528,4429 14896,4130 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id23">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11168" y="5955" width="4231" height="1279"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 11581,6093 L 14985,7095"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 11168,5972 L 11557,6243 11642,5955 11168,5972 Z"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 15398,7216 L 15009,6945 14924,7233 15398,7216 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id24">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="9893" y="2773" width="757" height="2671"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 10508,5027 L 10034,3189"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 10616,5443 L 10649,4970 10358,5045 10616,5443 Z"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 9926,2773 L 9893,3246 10184,3171 9926,2773 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id25">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="11007" y="6346" width="1732" height="3165"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 11213,6723 L 12532,9133"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 11007,6346 L 11091,6813 11355,6669 11007,6346 Z"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 12738,9510 L 12654,9043 12390,9187 12738,9510 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id26">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="8409" y="6500" width="2208" height="3377"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 10381,6860 L 8644,9516"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 10616,6500 L 10244,6795 10495,6959 10616,6500 Z"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 8409,9876 L 8781,9581 8530,9417 8409,9876 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id27">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="5055" y="5956" width="5009" height="1517"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 9651,6094 L 5467,7334"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 10063,5972 L 9589,5956 9674,6244 10063,5972 Z"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 5055,7456 L 5529,7472 5444,7184 5055,7456 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id28">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="5557" y="3401" width="4668" height="2197"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 9835,5414 L 5946,3584"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 10224,5597 L 9881,5270 9753,5541 10224,5597 Z"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 5557,3401 L 5900,3728 6028,3457 5557,3401 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id29">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="3539" y="24714" width="13721" height="1676"/>
|
||||
<path fill="rgb(255,229,202)" stroke="none" d="M 10399,26388 L 3540,26388 3540,24715 17258,24715 17258,26388 10399,26388 Z"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 10399,26388 L 3540,26388 3540,24715 17258,24715 17258,26388 10399,26388 Z"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="4372" y="25416"><tspan fill="rgb(206,24,30)" stroke="none">Unconnected nodes in the network can be </tspan></tspan><tspan class="TextPosition" x="3790" y="26127"><tspan fill="rgb(206,24,30)" stroke="none">reached through nodes in the bin they fall into </tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.CustomShape">
|
||||
<g id="id30">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="1037" y="4394" width="1109" height="1062"/>
|
||||
<path fill="rgb(114,159,207)" stroke="none" d="M 1590,4395 C 1903,4395 2143,4624 2143,4924 2143,5224 1903,5453 1590,5453 1277,5453 1038,5224 1038,4924 1038,4624 1277,4395 1590,4395 Z M 1038,4395 L 1038,4395 Z M 2144,5454 L 2144,5454 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 1590,4395 C 1903,4395 2143,4624 2143,4924 2143,5224 1903,5453 1590,5453 1277,5453 1038,5224 1038,4924 1038,4624 1277,4395 1590,4395 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 1038,4395 L 1038,4395 Z"/>
|
||||
<path fill="none" stroke="rgb(52,101,164)" d="M 2144,5454 L 2144,5454 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.TextShape">
|
||||
<g id="id31">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="774" y="3549" width="2120" height="963"/>
|
||||
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="1024" y="4250"><tspan fill="rgb(0,0,0)" stroke="none">11110</tspan></tspan></tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g class="com.sun.star.drawing.ConnectorShape">
|
||||
<g id="id32">
|
||||
<rect class="BoundingBox" stroke="none" fill="none" x="2144" y="4924" width="1807" height="2533"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 2394,5274 L 2425,5317"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 2455,5361 L 2486,5404"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 2517,5447 L 2548,5490"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 2579,5533 L 2610,5577"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 2640,5620 L 2671,5663"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 2702,5706 L 2733,5750"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 2764,5793 L 2794,5836"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 2825,5879 L 2856,5922"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 2887,5966 L 2918,6009"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 2949,6052 L 2979,6095"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 3010,6139 L 3041,6182"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 3072,6225 L 3103,6268"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 3134,6311 L 3164,6355"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 3195,6398 L 3226,6441"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 3257,6484 L 3288,6528"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 3319,6571 L 3349,6614"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 3380,6657 L 3411,6700"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 3442,6744 L 3473,6787"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 3504,6830 L 3534,6873"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 3565,6917 L 3596,6960"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 3627,7003 L 3658,7046"/>
|
||||
<path fill="none" stroke="rgb(0,0,0)" d="M 3689,7089 L 3700,7106"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 2144,4924 L 2283,5377 2527,5203 2144,4924 Z"/>
|
||||
<path fill="rgb(0,0,0)" stroke="none" d="M 3950,7456 L 3811,7003 3567,7177 3950,7456 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 57 KiB |
45
docs/swarm-guide/contents/index.rst
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
|
||||
Welcome to the Swarm documentation!
|
||||
===================================
|
||||
|
||||
.. image:: img/swarm.png
|
||||
:height: 300px
|
||||
:width: 238px
|
||||
:scale: 50 %
|
||||
:alt: swarm-logo
|
||||
:align: left
|
||||
|
||||
Swarm is a distributed storage platform and content distribution network, a native base layer service of the Ethereum **web3** stack.
|
||||
|
||||
This documentation was created with curious end-users, third web enterpreneurs and developers in mind. It should make you thoroughly educated about Swarm within a reasonable amount of time.
|
||||
|
||||
|
|
||||
|
||||
Table of contents:
|
||||
|
||||
.. toctree::
|
||||
:numbered:
|
||||
:maxdepth: 3
|
||||
|
||||
introduction
|
||||
installation
|
||||
gettingstarted
|
||||
up-and-download
|
||||
usage
|
||||
pss
|
||||
apireference
|
||||
configuration
|
||||
architecture
|
||||
resources
|
||||
|
||||
|
||||
This document is licensed under the @emph{Creative Commons Attribution License}. To
|
||||
view a copy of this license, visit http://creativecommons.org/licenses/by/2.0/
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
205
docs/swarm-guide/contents/installation.rst
Normal file
@ -0,0 +1,205 @@
|
||||
*************************
|
||||
Installation and Updates
|
||||
*************************
|
||||
|
||||
Swarm is part of the Ethereum stack, the reference implementation is currently at POC3 (proof of concept 3), or version 0.3.x
|
||||
|
||||
|
||||
Swarm runs on all major platforms (Linux, macOS, Windows, Raspberry Pi, Android, iOS).
|
||||
|
||||
Swarm was written in golang and requires the go-ethereum client **geth** to run.
|
||||
|
||||
.. note::
|
||||
The swarm package has not been extensively tested on platforms other than Linux and macOS.
|
||||
|
||||
Installing Swarm on Ubuntu via PPA
|
||||
==================================
|
||||
|
||||
The simplest way to install Swarm on **Ubuntu distributions** is via the built in launchpad PPAs (Personal Package Archives). We provide a single PPA repository that contains our stable releases for Ubuntu versions trusty, xenial, bionic and cosmic.
|
||||
|
||||
To enable our launchpad repository please run:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ sudo apt-get install software-properties-common
|
||||
$ sudo add-apt-repository -y ppa:ethereum/ethereum
|
||||
|
||||
After that you can install the stable version of Swarm:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install ethereum-swarm
|
||||
|
||||
Setting up Swarm in Docker
|
||||
=============================
|
||||
|
||||
You can run Swarm in a Docker container. The official Swarm Docker image including documentation on how to run it can be found on `Github <https://github.com/ethersphere/swarm-docker/>`_ or pulled from `Docker <https://hub.docker.com/r/ethdevops/swarm/>`_.
|
||||
|
||||
You can run it with optional arguments, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ docker run -e PASSWORD=<password> -t ethdevops/swarm:latest --debug --verbosity 4
|
||||
|
||||
In order to up/download, you need to expose the HTTP api port (here: to localhost:8501) and set the HTTP address:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ docker run -p 8501:8500/tcp -e PASSWORD=<password> -t ethdevops/swarm:latest --httpaddr=0.0.0.0 --debug --verbosity 4
|
||||
|
||||
In this example, you can use ``swarm --bzzapi http://localhost:8501 up testfile.md`` to upload ``testfile.md`` to swarm using the Docker node, and you can get it back e.g. with ``curl http://localhost:8501/bzz:/<hash>``.
|
||||
|
||||
Note that if you want to use a pprof HTTP server, you need to expose the ports and set the address (with ``--pprofaddr=0.0.0.0``) too.
|
||||
|
||||
In order to attach a Geth Javascript console, you need to mount a data directory from a volume:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ docker run -p 8501:8500/tcp -e PASSWORD=<password> -e DATADIR=/data -v /tmp/hostdata:/data -t-t ethdevops/swarm:latest --httpaddr=0.0.0.0 --debug --verbosity 4
|
||||
|
||||
Then, you can attach the console with:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ docker exec -it swarm1 /geth attach /data/bzzd.ipc
|
||||
|
||||
You can also open a terminal session inside the container:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ docker exec -it swarm1 /bin/sh
|
||||
|
||||
Installing Swarm from source
|
||||
=============================
|
||||
|
||||
The Swarm source code for can be found on https://github.com/ethersphere/swarm
|
||||
|
||||
Prerequisites: Go and Git
|
||||
--------------------------
|
||||
|
||||
Building the Swarm binary requires the following packages:
|
||||
|
||||
* go: https://golang.org
|
||||
* git: http://git.org
|
||||
|
||||
|
||||
Grab the relevant prerequisites and build from source.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Ubuntu / Debian
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ sudo apt install git
|
||||
|
||||
$ sudo add-apt-repository ppa:gophers/archive
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install golang-1.11-go
|
||||
|
||||
// Note that golang-1.11-go puts binaries in /usr/lib/go-1.11/bin. If you want them on your PATH, you need to make that change yourself.
|
||||
|
||||
$ export PATH=/usr/lib/go-1.11/bin:$PATH
|
||||
|
||||
.. tab:: Archlinux
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ pacman -S git go
|
||||
|
||||
.. tab:: Generic Linux
|
||||
|
||||
The latest version of Go can be found at https://golang.org/dl/
|
||||
|
||||
To install it, download the tar.gz file for your architecture and unpack it to ``/usr/local``
|
||||
|
||||
.. tab:: macOS
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ brew install go git
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
Take a look `here <https://medium.freecodecamp.org/setting-up-go-programming-language-on-windows-f02c8c14e2f>`_ at installing go and git and preparing your go environment under Windows.
|
||||
|
||||
Configuring the Go environment
|
||||
-------------------------------
|
||||
|
||||
You should then prepare your Go environment.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Linux
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ mkdir $HOME/go
|
||||
$ echo 'export GOPATH=$HOME/go' >> ~/.bashrc
|
||||
$ echo 'export PATH=$GOPATH/bin:$PATH' >> ~/.bashrc
|
||||
$ source ~/.bashrc
|
||||
|
||||
.. group-tab:: macOS
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ mkdir $HOME/go
|
||||
$ echo 'export GOPATH=$HOME/go' >> $HOME/.bash_profile
|
||||
$ echo 'export PATH=$GOPATH/bin:$PATH' >> $HOME/.bash_profile
|
||||
$ source $HOME/.bash_profile
|
||||
|
||||
Download and install Geth
|
||||
----------------------------------------
|
||||
|
||||
Once all prerequisites are met, download and install Geth from https://github.com/ethereum/go-ethereum
|
||||
|
||||
|
||||
Compiling and installing Swarm
|
||||
----------------------------------------
|
||||
|
||||
Once all prerequisites are met, and you have ``geth`` on your system, clone the Swarm git repo and build from source:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ git clone https://github.com/ethersphere/swarm
|
||||
$ cd swarm
|
||||
$ make swarm
|
||||
|
||||
Alternatively you could also use the Go tooling and download and compile Swarm from `master` via:
|
||||
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ go get -d github.com/ethersphere/swarm
|
||||
$ go install github.com/ethersphere/swarm/cmd/swarm
|
||||
|
||||
You can now run ``swarm`` to start your Swarm node.
|
||||
Let's check if the installation of ``swarm`` was successful:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
swarm version
|
||||
|
||||
If your ``PATH`` is not set and the ``swarm`` command cannot be found, try:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ $GOPATH/bin/swarm version
|
||||
|
||||
This should return some relevant information. For example:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
Swarm
|
||||
Version: 0.3
|
||||
Network Id: 0
|
||||
Go Version: go1.10.1
|
||||
OS: linux
|
||||
GOPATH=/home/user/go
|
||||
GOROOT=/usr/local/go
|
||||
|
||||
Updating your client
|
||||
---------------------
|
||||
|
||||
To update your client simply download the newest source code and recompile.
|
187
docs/swarm-guide/contents/introduction.rst
Normal file
@ -0,0 +1,187 @@
|
||||
*******************
|
||||
Introduction
|
||||
*******************
|
||||
|
||||
This guide walks you through understanding, installing, and using Swarm. It also explains how to contribute or ask for help. Swarm is in active development and it is untested in the wild, so use it at your own risk. To interact with Swarm, you will need to use the command line. If you are not comfortable using it, it might be worth to familiarise yourself `here <http://linuxcommand.org/lc3_lts0010.php>`_ first.
|
||||
|
||||
What is Swarm?
|
||||
==============
|
||||
|
||||
.. * extension allows for per-format preference for image format
|
||||
|
||||
.. image:: img/swarm.png
|
||||
:height: 300px
|
||||
:width: 238px
|
||||
:scale: 50 %
|
||||
:alt: swarm-logo
|
||||
:align: left
|
||||
|
||||
|
||||
Swarm is a distributed storage platform and content distribution service, a native base layer service of the ethereum :dfn:`web3` stack. The primary objective of Swarm is to provide a sufficiently decentralized and redundant store of Ethereum's public record, in particular to store and distribute dapp code and data as well as blockchain data. From an economic point of view, it allows participants to efficiently pool their storage and bandwidth resources in order to provide these services to all participants of the network, all while being incentivised by Ethereum.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/VgTZV471WFM" style="margin-bottom: 30px;" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
|
||||
|
||||
Objective
|
||||
==========
|
||||
|
||||
Swarm's broader objective is to provide infrastructure services for developers of decentralised web applications (dapps), notably: messaging, data streaming, peer to peer accounting, mutable resource updates, storage insurance, proof of custody scan and repair, payment channels and database services.
|
||||
|
||||
From the end user's perspective, Swarm is not that different from the world wide web, with the exception that uploads are not hosted on a specific server. Swarm offers a peer-to-peer storage and serving solution that is DDoS-resistant, has zero-downtime, fault-tolerant and censorship-resistant as well as self-sustaining due to a built-in incentive system which uses peer-to-peer accounting and allows trading resources for payment. Swarm is designed to deeply integrate with the devp2p multiprotocol network layer of Ethereum as well as with the Ethereum blockchain for domain name resolution (using ENS), service payments and content availability insurance.
|
||||
|
||||
Please refer to our `development roadmap <https://github.com/ethersphere/swarm/wiki/roadmap>`_ to stay informed with our progress.
|
||||
|
||||
Overview
|
||||
========================
|
||||
|
||||
Swarm is set out to provide base layer infrastructure for a new decentralised internet.
|
||||
Swarm is a peer-to-peer network of nodes providing distributed digital services by contributing resources (storage, message forwarding, payment processing) to each other. These contributions are accurately accounted for on a peer to peer basis, allowing nodes to trade resource for resource, but offering monetary compensation to nodes consuming less than they serve.
|
||||
|
||||
.. image:: img/swarm-intro.svg
|
||||
:alt: Swarm storage and message routing
|
||||
:width: 500
|
||||
|
||||
The Ethereum Foundation operates a Swarm testnet that can be used to test out functionality in a similar manner to the Ethereum testnet (ropsten).
|
||||
Everyone can join the network by running the Swarm client node on their server, desktop, laptop or mobile device. See :ref:`Getting Started` for how to do this.
|
||||
The Swarm client is part of the Ethereum stack, the reference implementation is written in golang and found under the ethersphere/swarm repository. Currently at POC (proof of concept) version 0.3 is running on all nodes.
|
||||
|
||||
Uploaded content is **not guaranteed to persist on the testnet** until storage insurance is implemented (see `Roadmap <https://github.com/orgs/ethersphere/projects/5>`_ for more details). All participating nodes should consider participation a voluntary service with no formal obligation whatsoever and should be expected to delete content at their will. Therefore, users should **under no circumstances regard Swarm as safe storage** until the incentive system is functional.
|
||||
|
||||
Swarm offers a **local HTTP proxy** API that dapps or command line tools can use to interact with Swarm. Some modules like `messaging <PSS>`_ are only available through RPC-JSON API. The foundation servers on the testnet are offering public gateways, which serve to easily demonstrate functionality and allow free access so that people can try Swarm without even running their own node.
|
||||
|
||||
Swarm is a collection of nodes of the devp2p network each of which run the BZZ URL schemes on the same network id.
|
||||
|
||||
Swarm nodes can also connect with one (or several) Ethereum blockchains for domain name resolution and one ethereum blockchain for bandwidth and storage compensation.
|
||||
Nodes running the same network id are supposed to connect to the same blockchain for payments. A Swarm network is identified by its network id which is an arbitrary integer.
|
||||
|
||||
Swarm allows for :dfn:`upload and disappear` which means that any node can just upload content to the Swarm and then is allowed to go offline. As long as nodes do not drop out or become unavailable, the content will still be accessible due to the 'synchronization' procedure in which nodes continuously pass along available data between each other.
|
||||
|
||||
Swarm supports encryption. Upload of unencrypted sensitive and private data is highly discouraged as **there is no way to undo an upload**. Users should refrain from uploading illegal, controversial or unethical content.
|
||||
|
||||
Always use encryption for sensitive content. For encrypted content, uploaded data is 'protected', i.e. only those that know the reference to the root chunk (the Swarm hash of the file) as well as the decryption key can access the content. Since publishing this reference (on ENS or with Feeds) requires an extra step, users are mildly protected against careless publishing as long as they use encryption. Even though there is no guarantees for removal, unaccessed content that is not explicitly insured will eventually disappear from the Swarm, as nodes will be incentivised to garbage collect it in case of storage capacity limits.
|
||||
|
||||
Swarm is a `Persistent Data Structure <https://en.wikipedia.org/wiki/Persistent_data_structure>`_, therefore there is no notion of delete/remove action in Swarm. This is because content is disseminated to Swarm nodes who are incentivised to serve it.
|
||||
|
||||
.. important:: It is not possible to **delete or remove** content uploaded to Swarm. **Always encrypt** sensitive content using the integrated Swarm encryption.
|
||||
|
||||
Available APIs
|
||||
================
|
||||
|
||||
Swarm offers several APIs:
|
||||
* CLI
|
||||
* JSON-RPC - using web3 bindings over Geth's IPC
|
||||
* HTTP interface - every Swarm node exposes a local HTTP proxy that implements the :ref:`BZZ URL schemes`
|
||||
* Javascript - available through the `erebos <https://erebos.js.org>`_, `swarm-js <https://github.com/MaiaVictor/swarm-js>`_ or `swarmgw <https://www.npmjs.com/package/swarmgw>`_ packages
|
||||
|
||||
|
||||
Code
|
||||
========
|
||||
|
||||
Source code is located at https://github.com/ethersphere/swarm/.
|
||||
|
||||
Roadmap
|
||||
=======
|
||||
|
||||
Roadmap is located at https://github.com/ethersphere/Swarm/wiki/roadmap
|
||||
|
||||
.. important:: Swarm is experimental code and untested in the wild. Use with extreme care. We encourage developers to connect to the testnet with their permanent nodes and give us feedback.
|
||||
|
||||
Public gateways
|
||||
===============
|
||||
|
||||
Swarm offers a local HTTP proxy API that Dapps can use to interact with Swarm. The Ethereum Foundation is hosting a public gateway, which allows free access so that people can try Swarm without running their own node.
|
||||
|
||||
The Swarm public gateway can be found at https://swarm-gateways.net and is always running the latest `stable` Swarm release.
|
||||
|
||||
.. important:: Swarm public gateways are temporary and users should not rely on their existence for production services.
|
||||
|
||||
Example Dapps
|
||||
=============
|
||||
|
||||
* https://swarm-gateways.net/bzz://swarmapps.eth
|
||||
* source code: https://github.com/ethersphere/swarm-dapps
|
||||
|
||||
Reporting a bug and contributing
|
||||
================================
|
||||
|
||||
Issues are tracked on github and github only. Swarm related issues and PRs have labels prefixed with *swarm*:
|
||||
|
||||
* https://github.com/ethersphere/swarm/issues
|
||||
* `Good first issues <https://github.com/ethersphere/swarm/issues?utf8=✓&q=is%3Aopen+is%3Aissue+label%3A"good+first+issue">`_
|
||||
|
||||
Please include the commit and branch when reporting an issue.
|
||||
|
||||
Pull requests should by default commit on the `master` branch.
|
||||
|
||||
Prospective contributors please read the `Contributing` section from our readme: https://github.com/ethersphere/swarm#contributing.
|
||||
|
||||
Credits
|
||||
===============
|
||||
|
||||
Swarm is funded by the Ethereum Foundation and industry sponsors.
|
||||
|
||||
The Core team
|
||||
----------------
|
||||
|
||||
* Viktor Trón - @zelig
|
||||
* Daniel A. Nagy - @nagydani
|
||||
* Aron Fischer - @homotopycolimit
|
||||
* Louis Holbrook - @nolash
|
||||
* Lewis Marshal - @lmars
|
||||
* Fabio Barone - @holisticode
|
||||
* Anton Evangelatov - @nonsense
|
||||
* Janoš Guljaš - @janos
|
||||
* Balint Gabor - @gbalint
|
||||
* Elad Nachmias - @justelad
|
||||
|
||||
were on the core team:
|
||||
|
||||
* Zahoor Mohamed - @jmozah
|
||||
* Zsolt Felföldi - @zsfelfoldi
|
||||
* Nick Johnson - @Arachnid
|
||||
|
||||
Sponsors and collaborators
|
||||
-----------------------------
|
||||
|
||||
* http://status.im
|
||||
* http://livepeer.org
|
||||
* http://jaak.io
|
||||
* http://datafund.io
|
||||
* http://mainframe.com
|
||||
* http://wolk.com
|
||||
* http://riat.at
|
||||
* http://datafund.org
|
||||
* http://216.com
|
||||
* http://cofound.it
|
||||
* http://iconomi.net
|
||||
* http://infura.io
|
||||
* http://epiclabs.io
|
||||
* http://asseth.fr
|
||||
|
||||
|
||||
Special thanks
|
||||
------------------
|
||||
|
||||
* Felix Lange, Alex Leverington for inventing and implementing devp2p/rlpx
|
||||
* Jeffrey Wilcke, Peter Szilagyi and the entire ethereum foundation go team for continued support, testing and direction
|
||||
* Gavin Wood and Vitalik Buterin for the holy trinity vision of web3
|
||||
* Nick Johnson for ENS and ENS Swarm integration
|
||||
* Alex Van der Sande, Fabian Vogelsteller, Bas van Kervel, Victor Maia, Everton Fraga and the Mist team
|
||||
* Elad Verbin for his continued technical involvement as an advisor and ideator
|
||||
* Nick Savers for his unrelenting support and meticulous reviews of our papers
|
||||
* Gregor Zavcer, Alexei Akhunov, Alex Beregszaszi, Daniel Varga, Julien Boutloup for inspiring discussions and ideas
|
||||
* Juan Benet and the IPFS team for continued inspiration
|
||||
* Carl Youngblood, Shane Howley, Paul Le Cam, Doug Leonard and the mainframe team for their contribution to PSS and Feeds
|
||||
* Sourabh Niyogi and the entire Wolk team for the inspiring collaboration on databases
|
||||
* Ralph Pilcher for implementing the swap swear and swindle contract suite in solidity/truffle and Oren Sokolowsky for the initial version
|
||||
* Javier Peletier from Epiclabs (ethergit) for his contribution to Feeds
|
||||
* Jarrad Hope and Carl Bennet (Status) for their support
|
||||
* Participants of the orange lounge research group and the Swarm orange summits
|
||||
* Roman Mandeleil and Anton Nashatyrev for an early java implementation of swarm
|
||||
* Igor Sharudin, Dean Vaessen for example dapps
|
||||
* Community contributors for feedback and testing
|
||||
* Daniel Kalman, Benjamin Kampmann, Daniel Lengyel, Anand Jaisingh for contributing to the swarm websites
|
||||
* Felipe Santana, Paolo Perez and Paratii team for filming at the 2017 swarm summit and making the summit website
|
120
docs/swarm-guide/contents/pss.rst
Normal file
@ -0,0 +1,120 @@
|
||||
*******************************
|
||||
PSS
|
||||
*******************************
|
||||
|
||||
:dfn:`pss` (Postal Service over Swarm) is a messaging protocol over Swarm with strong privacy features.
|
||||
The pss API is exposed through a JSON RPC interface described in the `API Reference <./apireference.rst#PSS>`_,
|
||||
here we explain the basic concepts and features.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
``pss`` is still an experimental feature and under active development and is available as of POC3 of Swarm. Expect things to change.
|
||||
|
||||
.. note::
|
||||
|
||||
There is no CLI support for ``pss``.
|
||||
|
||||
|
||||
Basics
|
||||
=============
|
||||
|
||||
With ``pss`` you can send messages to any node in the Swarm network. The messages are routed in the same manner as retrieve requests for chunks. Instead of chunk hash reference, ``pss`` messages specify a destination in the overlay address space independently of the message payload. This destination can describe a *specific node* if it is a complete overlay address or a *neighbourhood* if it is partially specified one. Up to the destination, the message is relayed through devp2p peer connections using :dfn:`forwarding kademlia` (passing messages via semi-permanent peer-to-peer TCP connections between relaying nodes using kademlia routing). Within the destination neighbourhood the message is broadcast using gossip.
|
||||
|
||||
Since ``pss`` messages are encrypted, ultimately *the recipient is whoever can decrypt the message*. Encryption can be done using asymmetric or symmetric encryption methods.
|
||||
|
||||
The message payload is dispatched to *message handlers* by the recipient nodes and dispatched to subscribers via the API.
|
||||
|
||||
.. important::
|
||||
``pss`` does not guarantee message ordering (`Best-effort delivery <https://en.wikipedia.org/wiki/Best-effort_delivery>`_)
|
||||
nor message delivery (e.g. messages to offline nodes will not be cached and replayed) at the moment.
|
||||
|
||||
Privacy features
|
||||
------------------
|
||||
|
||||
Thanks to end-to-end encryption, pss caters for private communication.
|
||||
|
||||
Due to forwarding kademlia, ``pss`` offers sender anonymity.
|
||||
|
||||
Using partial addressing, ``pss`` offers a sliding scale of recipient anonymity: the larger the destination neighbourhood (the smaller prefix you reveal of the intended recipient overlay address), the more difficult it is to identify the real recipient. On the other hand, since dark routing is inefficient, there is a trade-off between anonymity on the one hand and message delivery latency and bandwidth (and therefore cost) on the other. This choice is left to the application.
|
||||
|
||||
Forward secrecy is provided if you use the `Handshakes` module.
|
||||
|
||||
Usage
|
||||
===========================
|
||||
|
||||
See the `API Reference <./apireference.rst#PSS>`_ for details.
|
||||
|
||||
Registering a recipient
|
||||
--------------------------
|
||||
|
||||
Intended recipients first need to be registered with the node. This registration includes the following data:
|
||||
|
||||
1. ``Encryption key`` - can be a ECDSA public key for asymmetric encryption or a 32 byte symmetric key.
|
||||
|
||||
2. ``Topic`` - an arbitrary 4 byte word.
|
||||
|
||||
3. ``Address``- destination (fully or partially specified Swarm overlay address) to use for deterministic routing.
|
||||
|
||||
The registration returns a key id which is used to refer to the stored key in subsequent operations.
|
||||
|
||||
After you associate an encryption key with an address they will be checked against any message that comes through (when sending or receiving) given it matches the topic and the destination of the message.
|
||||
|
||||
Sending a message
|
||||
------------------
|
||||
|
||||
There are a few prerequisites for sending a message over ``pss``:
|
||||
|
||||
1. ``Encryption key id`` - id of the stored recipient's encryption key.
|
||||
|
||||
2. ``Topic`` - an arbitrary 4 byte word (with the exception of ``0x0000`` to be reserved for ``raw`` messages).
|
||||
|
||||
3. ``Message payload`` - the message data as an arbitrary byte sequence.
|
||||
|
||||
.. note::
|
||||
The Address that is coupled with the encryption key is used for routing the message.
|
||||
This does *not* need to be a full address; the network will route the message to the best
|
||||
of its ability with the information that is available.
|
||||
If *no* address is given (zero-length byte slice), routing is effectively deactivated,
|
||||
and the message is passed to all peers by all peers.
|
||||
|
||||
Upon sending the message it is encrypted and passed on from peer to peer. Any node along the route that can successfully decrypt the message is regarded as a recipient. If the destination is a neighbourhood, the message is passed around so ultimately it reaches the intended recipient which also forwards the message to their peers, recipients will continue to pass on the message to their peers, to make it harder for anyone spying on the traffic to tell where the message "ended up."
|
||||
|
||||
After you associate an encryption key with a destination they will be checked against any message that comes through (when sending or receiving) given it matches the topic and the address in the message.
|
||||
|
||||
.. important::
|
||||
When using the internal encryption methods, you MUST associate keys (whether symmetric or asymmetric) with an address space AND a topic before you will be able to send anything.
|
||||
|
||||
Sending a raw message
|
||||
----------------------
|
||||
|
||||
It is also possible to send a message without using the builtin encryption. In this case no recipient registration is made, but the message is sent directly, with the following input data:
|
||||
|
||||
1. ``Message payload`` - the message data as an arbitrary byte sequence.
|
||||
|
||||
2. ``Address``- the Swarm overlay address to use for the routing.
|
||||
|
||||
Receiving messages
|
||||
--------------------
|
||||
|
||||
You can subscribe to incoming messages using a topic. Since subscription needs push notifications, the supported RPC transport interfaces are websockets and IPC.
|
||||
|
||||
.. important::
|
||||
``pss`` does not guarantee message ordering (`Best-effort delivery <https://en.wikipedia.org/wiki/Best-effort_delivery>`_)
|
||||
nor message delivery (e.g. messages to offline nodes will not be cached and replayed) at the moment.
|
||||
|
||||
Advanced features
|
||||
==================
|
||||
|
||||
.. note:: This functionalities are optional features in pss. They are compiled in by default, but can be omitted by providing the appropriate build tags.
|
||||
|
||||
Handshakes
|
||||
-----------
|
||||
|
||||
``pss`` provides a convenience implementation of Diffie-Hellman handshakes using ephemeral symmetric keys. Peers keep separate sets of keys for a limited amount of incoming and outgoing communications, and create and exchange new keys when the keys expire.
|
||||
|
||||
|
||||
Protocols
|
||||
-----------
|
||||
|
||||
A framework is also in place for making ``devp2p`` protocols available using ``pss`` connections. This feature is only available using the internal golang API, read more in the GoDocs or the codes.
|
297
docs/swarm-guide/contents/resources.rst
Normal file
@ -0,0 +1,297 @@
|
||||
*******************
|
||||
Resources
|
||||
*******************
|
||||
|
||||
Homepage
|
||||
--------
|
||||
|
||||
the *Swarm homepage* is accessible via Swarm at `theswarm.eth`. The page can be accessed through the public gateway on https://swarm.ethereum.org or https://swarm-gateways.net/bzz:/theswarm.eth/
|
||||
|
||||
Blogposts
|
||||
---------------
|
||||
|
||||
* `Announcement of POC3 <https://blog.ethereum.org/2018/06/21/announcing-swarm-proof-of-concept-release-3/>`_
|
||||
* `POC2 public alpha announcement <https://blog.ethereum.org/2016/12/15/Swarm-alpha-public-pilot-basics-Swarm/>`_
|
||||
|
||||
Swarm Orange Summit
|
||||
----------------------
|
||||
|
||||
* `Swarm summit 2018 promo video <https://swarm-gateways.net/bzz:/079b4f4155d7e8b5ee76e8dd4e1a6a69c5b483d499654f03d0b3c588571d6be9/>`_
|
||||
* `2018 May 7-11 Ljubljana <https://swarm-gateways.net/bzz:/swarm-orange-summit.eth/>`_
|
||||
* 2017 June 4-10 Berlin
|
||||
|
||||
|
||||
Orange papers
|
||||
--------------
|
||||
|
||||
* Viktor Trón, Aron Fischer, Dániel Nagy A and Zsolt Felföldi, Nick Johnson: swap, swear and swindle: incentive system for Swarm. May 2016 - https://swarm-gateways.net/bzz:/theswarm.eth/ethersphere/orange-papers/1/sw^3.pdf
|
||||
* Viktor Trón, Aron Fischer, Nick Johnson: smash-proof: auditable storage for Swarm secured by masked audit secret hash. May 2016 - https://swarm-gateways.net/bzz:/theswarm.eth/ethersphere/orange-papers/2/smash.pdf
|
||||
* Viktor Trón, Aron Fischer, Ralph Pilcher, Fabio Barone: swap swear and swindle games: scalable infrastructure for decentralised service economies. Work in progress. June 2018. - https://www.sharelatex.com/read/yszmsdqyqbvc, `pfd on swarm <https://swarm-gateways.net/bzz:/ca5f4684b380644c3042fe81f65b3b9a0668e2e3cff53578fb68af8043f3c0b6/>`_.
|
||||
* Viktor Trón, Aron Fischer, Daniel A. Nagy. Swarm: a decentralised peer-to-peer network for messaging and storage. Work in progress. June, 2018. - https://www.sharelatex.com/read/gxhwssqzgfpr; `pdf on swarm <https://swarm-gateways.net/bzz:/4f45ae847fc55afb8bfdc381bae0809a0ce29bafc07b41293838fc7afae95d34/>`_.
|
||||
* P.O.T. data structures and databases on swarm. In preparation.
|
||||
* Mutable Resource Updates. An off-chain scheme for versioning content in Swarm. In preparation.
|
||||
* Privacy on swarm. Encryption, access control, private browsing in Swarm. Tentative.
|
||||
* Analysis of attack resilience of swarm storage. Tentative.
|
||||
|
||||
Podcasts
|
||||
-------------
|
||||
https://oktahedron.diskordia.org/?podcast=oh003-Swarm
|
||||
|
||||
Videos
|
||||
--------------
|
||||
|
||||
Aron Fischer, Louis Holbrook, Daniel A. Nagy: Swarm Development Update - Devcon3 Cancun, November 2017
|
||||
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/kT7BgOH49Sk" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
|
||||
Viktor Trón and Aron Fischer - Swap, Swear and Swindle Games - Devcon3 Cancun, November 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/9Cgyhsjsfbg" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
|
||||
sw3 London
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/Bn65-bI-S1o" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
|
||||
Louis Holbrook: Resource Updates - EthCC, Paris, March 2018
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/CgvRFsezTI4" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
|
||||
Daniel A Nagy: Encryption in Swarm - EthCC, Paris, March 2018
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/ZW7E8KTplgg" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
Viktor Tron
|
||||
`Base layer infrastructure services for web3 <https://www.youtube.com/watch?v=JgOU9MdgTGM#t=31m00s>`_ - EthCC, Paris, March 2018
|
||||
|
||||
++++++++++++
|
||||
|
||||
|
||||
Louis Holbrook (Ethersphere, Jaak) PSS - Node to node Communication Over Swarm - Devcon3 Cancun, November 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/fNlO5XJv9mI" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
Daniel A Nagy - Scalable Responsive Đapps with Swarm and ENS - Devcon3 Cancun, November 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/y01YJ_e5oHw" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
Aron Fischer - Data retrieval in Swarm - Swarm Orange Summit, Berlin, June 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/moEbbjOUUHI" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
Zahoor Mohamed (EF, Swarm team): Swarm Fuse Demo - Ethereum Meetup, Berlin, June 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/LObSTf2jozM" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
Daniel Nagy: Network topology for distributed storage - Swarm Orange Summit, Berlin, June 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/kKoGcAzEnJQ" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
Fabian Vogelsteller - Swarm Integration in Mist - Swarm Orange Summit, Berlin, June 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/AFVeWiP4ibQ" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
Daniel Nagy (EF, Swarm team): Plausible Deniability (2 parts) - Swarm Orange Summit, Berlin, June 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/fOJgNPdwy18" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/dHCWaiHtxOw" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
Elad Verbin: Data structures and security on Swarm (2 parts) - Swarm orange summit, Berlin, June 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/h5msn6FcP5o" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/IjYkEypa-ww" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
Louis Holbrook (Ethersphere, Jaak): PSS - internode messaging protocol - Swarm Orange Summit, Berlin, June 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/x9Rs23itEXo" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
Viktor Tron - Distributed Database Services - Swarm Orange Summit 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/H9MclB0J6-A" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
Viktor Tron - network testing framework and visualisation - Ethereum Meetup, Berlin, June 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/-c_kTW_aNgg" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
Doug Petkanics (Livepeer): Realtime video streaming on Swarm - Swarm Orange Summit, Berlin, June 2017
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/MB-drzcRCD8" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
++++++++++++
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/pQjwySXLm6Y" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
|
||||
++++++++++++
|
||||
|
||||
Nick Johnson on the Ethereum Name System
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/pLDDbCZXvTE" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
|
||||
|
||||
++++++++++++
|
||||
|
||||
Viktor Trón, Aron Fischer: Swap, Swear and Swindle. Swarm Incentivisation.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/DZbhjnhP5g4" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
|
||||
|
||||
++++++++++++
|
||||
|
||||
Viktor Trón: Towards Web3 Infrastructure.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/RF8L6V_E-MM" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
|
||||
++++++++++++
|
||||
|
||||
Dániel A. Nagy: Developing Scalable Decentralized Applications for Swarm and Ethereum
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/xrw9rvee7rc" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
|
||||
++++++++++++
|
||||
|
||||
Aron Fischer, Dániel A. Nagy, Viktor Trón: Swarm - Ethereum.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/Y9kch84cbPA" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
|
||||
|
||||
++++++++++++
|
||||
|
||||
Viktor Trón, Nick Johnson: Swarm, web3, and the Ethereum Name Service.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/BAAAhZI7qRQ" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
|
||||
++++++++++++
|
||||
|
||||
Nagy Dániel, Trón Viktor: Ethereum és Swarm: okos szerződések és elosztott világháló.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/vD8PAJvhH-4" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
|
||||
++++++++++++
|
||||
|
||||
Dániel Nagy: Swarm: Distributed storage for Ethereum, the Turing-complete blockchain.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/N_vtxw6nfmQ" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
|
||||
++++++++++++
|
||||
|
||||
Viktor Trón, Dániel A. Nagy: Swarm. Ethereum Devcon1, London, November 2015.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/VOC45AgZG5Q" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
|
||||
|
||||
++++++++++++
|
||||
|
||||
Dániel A. Nagy: Keeping the public record safe and accessible. Ethereum Devcon0, Berlin, December 2014.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/QzYZQ03ON2o" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
498
docs/swarm-guide/contents/up-and-download.rst
Normal file
@ -0,0 +1,498 @@
|
||||
.. _updownload:
|
||||
|
||||
***************************
|
||||
Uploading and downloading
|
||||
***************************
|
||||
|
||||
.. contents::
|
||||
|
||||
Introduction
|
||||
==================================
|
||||
.. note:: This guide assumes you've installed the Swarm client and have a running node that listens by default on port 8500. See `Getting Started <./gettingstarted.html>`_ for details.
|
||||
|
||||
Arguably, uploading and downloading content is the raison d'être of Swarm. Uploading content consists of "uploading" content to your local Swarm node, followed by your local Swarm node "syncing" the resulting chunks of data with its peers in the network. Meanwhile, downloading content consists of your local Swarm node querying its peers in the network for the relevant chunks of data and then reassembling the content locally.
|
||||
|
||||
Uploading and downloading data can be done through the ``swarm`` command line interface (CLI) on the terminal or via the HTTP interface on `http://localhost:8500 <http://localhost:8500>`_.
|
||||
|
||||
Using HTTP
|
||||
======================
|
||||
|
||||
Swarm offers an HTTP API. Thus, a simple way to upload and download files to/from Swarm is through this API.
|
||||
We can use the ``curl`` `tool <https://curl.haxx.se/docs/httpscripting.html>`_ to exemplify how to interact with this API.
|
||||
|
||||
.. note:: Files can be uploaded in a single HTTP request, where the body is either a single file to store, a tar stream (application/x-tar) or a multipart form (multipart/form-data).
|
||||
|
||||
To upload a single file to your node, run this:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl -H "Content-Type: text/plain" --data "some-data" http://localhost:8500/bzz:/
|
||||
|
||||
Once the file is uploaded, you will receive a hex string which will look similar to this:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
027e57bcbae76c4b6a1c5ce589be41232498f1af86e1b1a2fc2bdffd740e9b39
|
||||
|
||||
This is the Swarm hash of the address string of your content inside Swarm. It is the same hash that would have been returned by using the :ref:``swarm up <swarmup>`` command.
|
||||
|
||||
To download a file from Swarm, you just need the file's Swarm hash. Once you have it, the process is simple. Run:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl http://localhost:8500/bzz:/027e57bcbae76c4b6a1c5ce589be41232498f1af86e1b1a2fc2bdffd740e9b39/
|
||||
|
||||
The result should be your file:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
some-data
|
||||
|
||||
And that's it.
|
||||
|
||||
.. note:: If you omit the trailing slash from the url then the request will result in a HTTP redirect. The semantically correct way to access the root path of a Swarm manifest is using the trailing slash.
|
||||
|
||||
Tar stream upload
|
||||
------------------
|
||||
|
||||
Tar is a traditional unix/linux file format for packing a directory structure into a single file. Swarm provides a convenient way of using this format to make it possible to perform recursive uploads using the HTTP API.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
# create two directories with a file in each
|
||||
$ mkdir dir1 dir2
|
||||
$ echo "some-data" > dir1/file.txt
|
||||
$ echo "some-data" > dir2/file.txt
|
||||
|
||||
# create a tar archive containing the two directories (this will tar everything in the working directory)
|
||||
tar cf files.tar .
|
||||
|
||||
# upload the tar archive to Swarm to create a manifest
|
||||
$ curl -H "Content-Type: application/x-tar" --data-binary @files.tar http://localhost:8500/bzz:/
|
||||
> 1e0e21894d731271e50ea2cecf60801fdc8d0b23ae33b9e808e5789346e3355e
|
||||
|
||||
You can then download the files using:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl http://localhost:8500/bzz:/1e0e21894d731271e50ea2cecf60801fdc8d0b23ae33b9e808e5789346e3355e/dir1/file.txt
|
||||
> some-data
|
||||
|
||||
$ curl http://localhost:8500/bzz:/1e0e21894d731271e50ea2cecf60801fdc8d0b23ae33b9e808e5789346e3355e/dir2/file.txt
|
||||
> some-data
|
||||
|
||||
GET requests work the same as before with the added ability to download multiple files by setting `Accept: application/x-tar`:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl -s -H "Accept: application/x-tar" http://localhost:8500/bzz:/ccef599d1a13bed9989e424011aed2c023fce25917864cd7de38a761567410b8/ | tar t
|
||||
> dir1/file.txt
|
||||
dir2/file.txt
|
||||
|
||||
|
||||
Multipart form upload
|
||||
---------------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl -F 'dir1/file.txt=some-data;type=text/plain' -F 'dir2/file.txt=some-data;type=text/plain' http://localhost:8500/bzz:/
|
||||
> 9557bc9bb38d60368f5f07aae289337fcc23b4a03b12bb40a0e3e0689f76c177
|
||||
|
||||
$ curl http://localhost:8500/bzz:/9557bc9bb38d60368f5f07aae289337fcc23b4a03b12bb40a0e3e0689f76c177/dir1/file.txt
|
||||
> some-data
|
||||
|
||||
$ curl http://localhost:8500/bzz:/9557bc9bb38d60368f5f07aae289337fcc23b4a03b12bb40a0e3e0689f76c177/dir2/file.txt
|
||||
> some-data
|
||||
|
||||
|
||||
Add files to an existing manifest using multipart form
|
||||
------------------------------------------------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl -F 'dir3/file.txt=some-other-data;type=text/plain' http://localhost:8500/bzz:/9557bc9bb38d60368f5f07aae289337fcc23b4a03b12bb40a0e3e0689f76c177
|
||||
> ccef599d1a13bed9989e424011aed2c023fce25917864cd7de38a761567410b8
|
||||
|
||||
$ curl http://localhost:8500/bzz:/ccef599d1a13bed9989e424011aed2c023fce25917864cd7de38a761567410b8/dir1/file.txt
|
||||
> some-data
|
||||
|
||||
$ curl http://localhost:8500/bzz:/ccef599d1a13bed9989e424011aed2c023fce25917864cd7de38a761567410b8/dir3/file.txt
|
||||
> some-other-data
|
||||
|
||||
|
||||
Upload files using a simple HTML form
|
||||
-------------------------------------
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<form method="POST" action="/bzz:/" enctype="multipart/form-data">
|
||||
<input type="file" name="dir1/file.txt">
|
||||
<input type="file" name="dir2/file.txt">
|
||||
<input type="submit" value="upload">
|
||||
</form>
|
||||
|
||||
|
||||
Listing files
|
||||
-------------
|
||||
|
||||
.. note:: The ``jq`` command mentioned below is a separate application that can be used to pretty-print the json data retrieved from the ``curl`` request
|
||||
|
||||
A `GET` request with ``bzz-list`` URL scheme returns a list of files contained under the path, grouped into common prefixes which represent directories:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl -s http://localhost:8500/bzz-list:/ccef599d1a13bed9989e424011aed2c023fce25917864cd7de38a761567410b8/ | jq .
|
||||
> {
|
||||
"common_prefixes": [
|
||||
"dir1/",
|
||||
"dir2/",
|
||||
"dir3/"
|
||||
]
|
||||
}
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl -s http://localhost:8500/bzz-list:/ccef599d1a13bed9989e424011aed2c023fce25917864cd7de38a761567410b8/dir1/ | jq .
|
||||
> {
|
||||
"entries": [
|
||||
{
|
||||
"path": "dir1/file.txt",
|
||||
"contentType": "text/plain",
|
||||
"size": 9,
|
||||
"mod_time": "2017-03-12T15:19:55.112597383Z",
|
||||
"hash": "94f78a45c7897957809544aa6d68aa7ad35df695713895953b885aca274bd955"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Setting ``Accept: text/html`` returns the list as a browsable HTML document.
|
||||
|
||||
|
||||
Using CLI
|
||||
=====================
|
||||
|
||||
.. _swarmup:
|
||||
|
||||
Uploading a file to your local Swarm node
|
||||
------------------------------------------
|
||||
|
||||
.. note:: Once a file is uploaded to your local Swarm node, your node will `sync` the chunks of data with other nodes on the network. Thus, the file will eventually be available on the network even when your original node goes offline.
|
||||
|
||||
The basic command for uploading to your local node is ``swarm up FILE``. For example, let's create a file called example.md and issue the following command to upload the file example.md file to your local Swarm node.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ echo "this is an example" > example.md
|
||||
$ swarm up example.md
|
||||
> d1f25a870a7bb7e5d526a7623338e4e9b8399e76df8b634020d11d969594f24a
|
||||
|
||||
The hash returned is the hash of a :ref:`swarm manifest <swarm-manifest>`. This manifest is a JSON file that contains the ``example.md`` file as its only entry. Both the primary content and the manifest are uploaded by default.
|
||||
|
||||
After uploading, you can access this example.md file from Swarm by pointing your browser to:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ http://localhost:8500/bzz:/d1f25a870a7bb7e5d526a7623338e4e9b8399e76df8b634020d11d969594f24a/
|
||||
|
||||
The manifest makes sure you could retrieve the file with the correct MIME type.
|
||||
|
||||
You can encrypt your file using the ``--encrypt`` flag. See the :ref:`Encryption` section for details.
|
||||
|
||||
|
||||
Suppressing automatic manifest creation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
You may wish to prevent a manifest from being created alongside with your content and only upload the raw content. You might want to include it in a custom index, or handle it as a data-blob known and used only by a certain application that knows its MIME type. For this you can set ``--manifest=false``:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --manifest=false up FILE
|
||||
> 7149075b7f485411e5cc7bb2d9b7c86b3f9f80fb16a3ba84f5dc6654ac3f8ceb
|
||||
|
||||
This option suppresses automatic manifest upload. It uploads the content as-is.
|
||||
However, if you wish to retrieve this file, the browser can not be told unambiguously what that file represents.
|
||||
In the context, the hash ``7149075b7f485411e5cc7bb2d9b7c86b3f9f80fb16a3ba84f5dc6654ac3f8ceb`` does not refer to a manifest. Therefore, any attempt to retrieve it using the ``bzz:/`` scheme will result in a ``404 Not Found`` error. In order to access this file, you would have to use the :ref:`bzz-raw` scheme.
|
||||
|
||||
|
||||
Downloading a single file
|
||||
----------------------------
|
||||
|
||||
To download single files, use the ``swarm down`` command.
|
||||
Single files can be downloaded in the following different manners. The following examples assume ``<hash>`` resolves into a single-file manifest:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm down bzz:/<hash> #downloads the file at <hash> to the current working directory
|
||||
$ swarm down bzz:/<hash> file.tmp #downloads the file at <hash> as ``file.tmp`` in the current working dir
|
||||
$ swarm down bzz:/<hash> dir1/ #downloads the file at <hash> to ``dir1/``
|
||||
|
||||
You can also specify a custom proxy with `--bzzapi`:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzapi http://localhost:8500 down bzz:/<hash> #downloads the file at <hash> to the current working directory using the localhost node
|
||||
|
||||
|
||||
Downloading a single file from a multi-entry manifest can be done with (``<hash>`` resolves into a multi-entry manifest):
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm down bzz:/<hash>/index.html #downloads index.html to the current working directory
|
||||
$ swarm down bzz:/<hash>/index.html file.tmp #downloads index.html as file.tmp in the current working directory
|
||||
$ swarm down bzz:/<hash>/index.html dir1/ #downloads index.html to dir1/
|
||||
|
||||
..If you try to download from a multi-entry manifest without specifying the file, you will get a `got too many matches for this path` error. You will need to specify a `--recursive` flag (see below).
|
||||
|
||||
Uploading to a remote Swarm node
|
||||
-----------------------------------
|
||||
You can upload to a remote Swarm node using the ``--bzzapi`` flag.
|
||||
For example, you can use one of the public gateways as a proxy, in which case you can upload to Swarm without even running a node.
|
||||
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzapi https://swarm-gateways.net up example.md
|
||||
|
||||
.. note:: This gateway currently only accepts uploads of limited size. In future, the ability to upload to this gateways is likely to disappear entirely.
|
||||
|
||||
|
||||
Uploading a directory
|
||||
-----------------------
|
||||
|
||||
Uploading directories is achieved with the ``--recursive`` flag.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --recursive up /path/to/directory
|
||||
> ab90f84c912915c2a300a94ec5bef6fc0747d1fbaf86d769b3eed1c836733a30
|
||||
|
||||
The returned hash refers to a root manifest referencing all the files in the directory.
|
||||
|
||||
Directory with default entry
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It is possible to declare a default entry in a manifest. In the example above, if ``index.html`` is declared as the default, then a request for a resource with an empty path will show the contents of the file ``/index.html``
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --defaultpath /path/to/directory/index.html --recursive up /path/to/directory
|
||||
> ef6fc0747d1fbaf86d769b3eed1c836733a30ab90f84c912915c2a300a94ec5b
|
||||
|
||||
You can now access index.html at
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ http://localhost:8500/bzz:/ef6fc0747d1fbaf86d769b3eed1c836733a30ab90f84c912915c2a300a94ec5b/
|
||||
|
||||
and also at
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ http://localhost:8500/bzz:/ef6fc0747d1fbaf86d769b3eed1c836733a30ab90f84c912915c2a300a94ec5b/index.html
|
||||
|
||||
This is especially useful when the hash (in this case ``ef6fc0747d1fbaf86d769b3eed1c836733a30ab90f84c912915c2a300a94ec5b``) is given a registered name like ``mysite.eth`` in the `Ethereum Name Service <./ens.html>`_. In this case the lookup would be even simpler:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
http://localhost:8500/bzz:/mysite.eth/
|
||||
|
||||
.. note:: You can toggle automatic default entry detection with the ``SWARM_AUTO_DEFAULTPATH`` environment variable. You can do so by a simple ``$ export SWARM_AUTO_DEFAULTPATH=true``. This will tell Swarm to automatically look for ``<uploaded directory>/index.html`` file and set it as the default manifest entry (in the case it exists).
|
||||
|
||||
Downloading a directory
|
||||
--------------------------
|
||||
|
||||
To download a directory, use the ``swarm down --recursive`` command.
|
||||
Directories can be downloaded in the following different manners. The following examples assume <hash> resolves into a multi-entry manifest:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm down --recursive bzz:/<hash> #downloads the directory at <hash> to the current working directory
|
||||
$ swarm down --recursive bzz:/<hash> dir1/ #downloads the file at <hash> to dir1/
|
||||
|
||||
Similarly as with a single file, you can also specify a custom proxy with ``--bzzapi``:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzapi http://localhost:8500 down --recursive bzz:/<hash> #note the flag ordering
|
||||
|
||||
.. important :: Watch out for the order of arguments in directory upload/download: it's ``swarm --recursive up`` and ``swarm down --recursive``.
|
||||
|
||||
Adding entries to a manifest
|
||||
-------------------------------
|
||||
The command for modifying manifests is ``swarm manifest``.
|
||||
|
||||
To add an entry to a manifest, use the command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm manifest add <manifest-hash> <path> <hash> [content-type]
|
||||
|
||||
To remove an entry from a manifest, use the command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm manifest remove <manifest-hash> <path>
|
||||
|
||||
To modify the hash of an entry in a manifest, use the command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm manifest update <manifest-hash> <path> <new-hash>
|
||||
|
||||
Reference table
|
||||
-----------------
|
||||
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| **upload** | ``swarm up <file>`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| ~ dir | ``swarm --recursive up <dir>`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| ~ dir w/ default entry (here: index.html)| ``swarm --defaultpath <dir>/index.html --recursive up <dir>`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| ~ w/o manifest | ``swarm --manifest=false up`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| ~ to remote node | ``swarm --bzzapi https://swarm-gateways.net up`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| ~ with encryption | ``swarm up --encrypt`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| **download** | ``swarm down bzz:/<hash>`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| ~ dir | ``swarm down --recursive bzz:/<hash>`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| ~ as file | ``swarm down bzz:/<hash> file.tmp`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| ~ into dir | ``swarm down bzz:/<hash> dir/`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| ~ w/ custom proxy | ``swarm down --bzzapi http://<proxy address> down bzz:/<hash>`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| **manifest** | |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| add ~ | ``swarm manifest add <manifest-hash> <path> <hash> [content-type]`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| remove ~ | ``swarm manifest remove <manifest-hash> <path>`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
| update ~ | ``swarm manifest update <manifest-hash> <path> <new-hash>`` |
|
||||
+------------------------------------------+------------------------------------------------------------------------+
|
||||
|
||||
Up- and downloading in the CLI: example usage
|
||||
----------------------------------
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Up/downloading
|
||||
|
||||
Let's create a dummy file and upload it to Swarm:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ echo "this is a test" > myfile.md
|
||||
$ swarm up myfile.md
|
||||
> <reference hash>
|
||||
|
||||
We can download it using the ``bzz:/`` scheme and give it a name.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm down bzz:/<reference hash> iwantmyfileback.md
|
||||
$ cat iwantmyfileback.md
|
||||
> this is a test
|
||||
|
||||
We can also ``curl`` it using the HTTP API.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl http://localhost:8500/bzz:/<reference hash>/
|
||||
> this is a test
|
||||
|
||||
We can use the ``bzz-raw`` scheme to see the manifest of the upload.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl http://localhost:8500/bzz-raw:/<reference hash>/
|
||||
|
||||
This returns the manifest:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"hash": "<file hash>",
|
||||
"path": "myfile.md",
|
||||
"contentType": "text/markdown; charset=utf-8",
|
||||
"mode": 420,
|
||||
"size": 15,
|
||||
"mod_time": "<timestamp>"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. group-tab:: Up/down as is
|
||||
|
||||
We can upload the file as-is:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ echo "this is a test" > myfile.md
|
||||
$ swarm --manifest=false up myfile.md
|
||||
> <as-is reference hash>
|
||||
|
||||
We can retrieve it using the ``bzz-raw`` scheme in the HTTP API.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl http://localhost:8500/bzz-raw:/<as-is reference hash>/
|
||||
> this is a test
|
||||
|
||||
.. group-tab:: Manipulate manifests
|
||||
|
||||
Let's create a directory with a dummy file, and upload the directory to swarm.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ mkdir dir
|
||||
$ echo "this is a test" > dir/dummyfile.md
|
||||
$ swarm --recursive up dir
|
||||
> <dir hash>
|
||||
|
||||
We can look at the manifest using ``bzz-raw`` and the HTTP API.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl http://localhost:8500/bzz-raw:/<dir hash>/
|
||||
|
||||
It will look something like this:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"hash": "<file hash>",
|
||||
"path": "dummyfile.md",
|
||||
"contentType": "text/markdown; charset=utf-8",
|
||||
"mode": 420,
|
||||
"size": 15,
|
||||
"mod_time": "2018-11-11T16:52:07+01:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
We can remove the file from the manifest using ``manifest remove``.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm manifest remove <dir hash> "dummyfile.md"
|
||||
> <new dir hash>
|
||||
|
||||
When we check the new dir hash, we notice that it's empty -- as it should be.
|
||||
|
||||
Let's put the file back in there.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm up dir/dummyfile.md
|
||||
> <individual file hash>
|
||||
$ swarm manifest add <new dir hash> "dummyfileagain.md" <individual file hash>
|
||||
> <new dir hash 2>
|
||||
|
||||
We can check the manifest under <new dir hash 2> to see that the file is back there.
|
16
docs/swarm-guide/contents/usage.rst
Normal file
@ -0,0 +1,16 @@
|
||||
***************************
|
||||
Working with content
|
||||
***************************
|
||||
|
||||
In this chapter, we demonstrate features of Swarm related to storage and retrieval. First we discuss how to solve mutability of resources in a content addressed system using the Ethereum Name Service on the blockchain, then using Feeds in Swarm.
|
||||
Then we briefly discuss how to protect your data by restricting access using encryption.
|
||||
We also discuss in detail how files can be organised into collections using manifests and how this allows virtual hosting of websites. Another form of interaction with Swarm, namely mounting a Swarm manifest as a local directory using FUSE.
|
||||
We conclude by summarizing the various URL schemes that provide simple HTTP endpoints for clients to interact with Swarm.
|
||||
|
||||
.. include:: usage/ens.rst
|
||||
.. include:: usage/feed.rst
|
||||
.. include:: usage/manifests.rst
|
||||
.. include:: usage/encryption.rst
|
||||
.. include:: usage/act.rst
|
||||
.. include:: usage/fuse.rst
|
||||
.. include:: usage/bzz.rst
|
269
docs/swarm-guide/contents/usage/act.rst
Normal file
@ -0,0 +1,269 @@
|
||||
Access Control
|
||||
===============
|
||||
|
||||
Swarm supports restricting access to content through several access control strategies:
|
||||
|
||||
- Password protection - where a number of undisclosed parties can access content using a shared secret ``(pass, act)``
|
||||
|
||||
- Selective access using `Elliptic Curve <https://en.wikipedia.org/wiki/Elliptic-curve_cryptography>`_ key-pairs:
|
||||
|
||||
- For an undisclosed party - where only one grantee can access the content ``(pk)``
|
||||
|
||||
- For a number of undisclosed parties - where every grantee can access the content ``(act)``
|
||||
|
||||
**Creating** access control for content is currently supported only through CLI usage.
|
||||
|
||||
**Accessing** restricted content is available through CLI and HTTP. When accessing content which is restricted by a password `HTTP Basic access authentication <https://en.wikipedia.org/wiki/Basic_access_authentication>`_ can be used out-of-the-box.
|
||||
|
||||
.. important:: When accessing content which is restricted to certain EC keys - the node which exposes the HTTP proxy that is queried must be started with the granted private key as its ``bzzaccount`` CLI parameter.
|
||||
|
||||
Password protection
|
||||
-------------------
|
||||
|
||||
The simplest type of credential is a passphrase. In typical use cases, the
|
||||
passphrase is distributed by off-band means, with adequate security measures.
|
||||
Any user that knows the passphrase can access the content.
|
||||
|
||||
When using password protection, a given content reference (e.g.: a given Swarm manifest address or, alternatively,
|
||||
a Mutable Resource address) is encrypted using `scrypt <https://en.wikipedia.org/wiki/Scrypt>`_
|
||||
with a given passphrase and a random salt.
|
||||
The encrypted reference and the salt are then embedded into an unencrypted manifest which can be freely
|
||||
distributed but only accessed by undisclosed parties that posses knowledge of the passphrase.
|
||||
|
||||
Password protection can also be used for selective access when using the ``act`` strategy - similarly to granting access to a certain EC key access can be also given to a party identified by a password. In fact, one could also create an ``act`` manifest that solely grants access to grantees through passwords, without the need to know their public keys.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. important:: Restricting access to content on Swarm is a 2-step process - you first upload your content, then wrap the reference with an access control manifest. **We recommend that you always upload your content with encryption enabled**. In the following examples we will refer the uploaded content hash as ``reference hash``
|
||||
|
||||
First, we create a simple test file. We upload it to Swarm (with encryption).
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ echo "testfile" > mytest.txt
|
||||
$ swarm up --encrypt mytest.txt
|
||||
> <reference hash>
|
||||
|
||||
Then, for the sake of this example, we create a file with our password in it.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ echo "mypassword" > mypassword.txt
|
||||
|
||||
This password will protect the access-controlled content that we upload. We can refer to this password using the `--password` flag. The password file should contain the password in plaintext.
|
||||
|
||||
The ``swarm access`` command sets a new password using the ``new pass`` argument. It expects you to input the password file and the uploaded Swarm content hash you'd like to limit access to.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ swarm access new pass --password mypassword.txt <reference hash>
|
||||
> <reference of access controlled manifest>
|
||||
|
||||
The returned hash is the hash of the access controlled manifest.
|
||||
|
||||
When requesting this hash through the HTTP gateway you should receive an ``HTTP Unauthorized 401`` error:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ curl http://localhost:8500/bzz:/<reference of access controlled manifest>/
|
||||
> Code: 401
|
||||
> Message: cant decrypt - forbidden
|
||||
> Timestamp: XXX
|
||||
|
||||
You can retrieve the content in three ways:
|
||||
|
||||
1. The same request should make an authentication dialog pop-up in the browser. You could then input the password needed and the content should correctly appear. (Leave the username empty.)
|
||||
2. Requesting the same hash with HTTP basic authentication would return the content too. ``curl`` needs you to input a username as well as a password, but the former can be an arbitrary string (here, it's ``x``).
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ curl http://x:mypassword@localhost:8500/bzz:/<reference of access controlled manifest>/
|
||||
|
||||
3. You can also use ``swarm down`` with the ``--password`` flag.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ swarm --password mypassword.txt down bzz:/<reference of access controlled manifest>/ mytest2.txt
|
||||
$ cat mytest2.txt
|
||||
> testfile
|
||||
|
||||
Selective access using EC keys
|
||||
-------------------------------
|
||||
|
||||
A more sophisticated type of credential is an `Elliptic Curve <https://en.wikipedia.org/wiki/Elliptic-curve_cryptography>`_
|
||||
private key, identical to those used throughout Ethereum for accessing accounts.
|
||||
|
||||
In order to obtain the content reference, an
|
||||
`Elliptic-curve Diffie–Hellman <https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman>`_ `(ECDH)`
|
||||
key agreement needs to be performed between a provided EC public key (that of the content publisher)
|
||||
and the authorized key, after which the undisclosed authorized party can decrypt the reference to the
|
||||
access controlled content.
|
||||
|
||||
Whether using access control to disclose content to a single party (by using the ``pk`` strategy) or to
|
||||
multiple parties (using the ``act`` strategy), a third unauthorized party cannot find out the identity
|
||||
of the authorized parties.
|
||||
The third party can, however, know the number of undisclosed grantees to the content.
|
||||
This, however, can be mitigated by adding bogus grantee keys while using the ``act`` strategy
|
||||
in cases where masking the number of grantees is necessary. This is not the case when using the ``pk`` strategy, as it as
|
||||
by definition an agreement between two parties and only two parties (the publisher and the grantee).
|
||||
|
||||
.. important::
|
||||
Accessing content which is access controlled is enabled only when using a `local` Swarm node (e.g. running on `localhost`) in order to keep
|
||||
your data, passwords and encryption keys safe. This is enforced through an in-code guard.
|
||||
|
||||
.. danger::
|
||||
**NEVER (EVER!) use an external gateway to upload or download access controlled content as you will be putting your privacy at risk!
|
||||
You have been fairly warned!**
|
||||
|
||||
**Protecting content with Elliptic curve keys (single grantee):**
|
||||
|
||||
The ``pk`` strategy requires a ``bzzaccount`` to encrypt with. The most comfortable option in this case would be the same ``bzzaccount`` you normally start your Swarm node with - this will allow you to access your content seamlessly through that node at any given point in time.
|
||||
|
||||
Grantee public keys are expected to be in an *secp256 compressed* form - 66 characters long string (an example would be ``02e6f8d5e28faaa899744972bb847b6eb805a160494690c9ee7197ae9f619181db``). Comments and other characters are not allowed.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ swarm --bzzaccount <your account> access new pk --grant-key <your public key> <reference hash>
|
||||
> <reference of access controlled manifest>
|
||||
|
||||
The returned hash ``4b964a75ab19db960c274058695ca4ae21b8e19f03ddf1be482ba3ad3c5b9f9b`` is the hash of the access controlled manifest.
|
||||
|
||||
The only way to fetch the access controlled content in this case would be to request the hash through one of the nodes that were granted access and/or posses the granted private key (and that the requesting node has been started with the appropriate ``bzzaccount`` that is associated with the relevant key) - either the local node that was used to upload the content or the node which was granted access through its public key.
|
||||
|
||||
**Protecting content with Elliptic curve keys and passwords (multiple grantees):**
|
||||
|
||||
The ``act`` strategy also requires a ``bzzaccount`` to encrypt with. The most comfortable option in this case would be the same ``bzzaccount`` you normally start your Swarm node with - this will allow you to access your content seamlessly through that node at any given point in time
|
||||
|
||||
.. note:: the ``act`` strategy expects a grantee public-key list and/or a list of permitted passwords to be communicated to the CLI. This is done using the ``--grant-keys`` flag and/or the ``--password`` flag. Grantee public keys are expected to be in an *secp256 compressed* form - 66 characters long string (e.g. ``02e6f8d5e28faaa899744972bb847b6eb805a160494690c9ee7197ae9f619181db``). Each grantee should appear in a separate line. Passwords are also expected to be line-separated. Comments and other characters are not allowed.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1 access new act --grant-keys /path/to/public-keys/file --password /path/to/passwords/file <reference hash>
|
||||
4b964a75ab19db960c274058695ca4ae21b8e19f03ddf1be482ba3ad3c5b9f9b
|
||||
|
||||
The returned hash ``4b964a75ab19db960c274058695ca4ae21b8e19f03ddf1be482ba3ad3c5b9f9b`` is the hash of the access controlled manifest.
|
||||
|
||||
As with the ``pk`` strategy - the only way to fetch the access controlled content in this case would be to request the hash through one of the nodes that were granted access and/or posses the granted private key (and that the requesting node has been started with the appropriate ``bzzaccount`` that is associated with the relevant key) - either the local node that was used to upload the content or one of the nodes which were granted access through their public keys.
|
||||
|
||||
HTTP usage
|
||||
----------
|
||||
|
||||
Accessing restricted content on Swarm through the HTTP API is, as mentioned, limited to your local node
|
||||
due to security considerations.
|
||||
Whenever requesting a restricted resource without the proper credentials via the HTTP proxy, the Swarm node will respond
|
||||
with an ``HTTP 401 Unauthorized`` response code.
|
||||
|
||||
*When accessing password protected content:*
|
||||
|
||||
When accessing a resource protected by a passphrase without the appropriate credentials the browser will
|
||||
receive an ``HTTP 401 Unauthorized`` response and will show a pop-up dialog asking for a username and password.
|
||||
For the sake of decrypting the content - only the password input in the dialog matters and the username field can be left blank.
|
||||
|
||||
The credentials for accessing content protected by a password can be provided in the initial request in the form of:
|
||||
``http://x:<password>@localhost:8500/bzz:/<hash or ens name>`` (``curl`` needs you to input a username as well as a password, but the former can be an arbitrary string (here, it's ``x``).)
|
||||
|
||||
.. important:: Access controlled content should be accessed through the ``bzz://`` protocol
|
||||
|
||||
*When accessing EC key protected content:*
|
||||
|
||||
When accessing a resource protected by EC keys, the node that requests the content will try to decrypt the restricted
|
||||
content reference using its **own** EC key which is associated with the current `bzz account` that
|
||||
the node was started with (see the ``--bzzaccount`` flag). If the node's key is granted access - the content will be
|
||||
decrypted and displayed, otherwise - an ``HTTP 401 Unauthorized`` error will be returned by the node.
|
||||
|
||||
Access control in the CLI: example usage
|
||||
-----------------------------------------
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. group-tab:: Passwords
|
||||
|
||||
First, we create a simple test file. We upload it to Swarm using encryption.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ echo "testfile" > mytest.txt
|
||||
$ swarm up --encrypt mytest.txt
|
||||
> <reference hash>
|
||||
|
||||
Then, we define a password file and use it to create an access-controlled manifest.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ echo "mypassword" > mypassword.txt
|
||||
$ swarm access new pass --password mypassword.txt <reference hash>
|
||||
> <reference of access controlled manifest>
|
||||
|
||||
We can create a passwords file with one password per line in plaintext (``password1`` is probably not a very good password).
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ for i in {1..3}; do echo -e password$i; done > mypasswords.txt
|
||||
$ cat mypasswords.txt
|
||||
> password1
|
||||
> password2
|
||||
> password3
|
||||
|
||||
Then, we point to this list while wrapping our manifest.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ swarm access new act --password mypasswords.txt <reference hash>
|
||||
> <reference of access controlled manifest>
|
||||
|
||||
We can access the returned manifest using any of the passwords in the password list.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ echo password1 > password1.txt
|
||||
$ swarm --password1.txt down bzz:/<reference of access controlled manifest>
|
||||
|
||||
We can also `curl` it.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ curl http://:password1@localhost:8500/bzz:/<reference of access controlled manifest>/
|
||||
|
||||
.. group-tab:: Elliptic curve keys
|
||||
|
||||
1. ``pk`` strategy
|
||||
|
||||
First, we create a simple test file. We upload it to Swarm using encryption.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ echo "testfile" > mytest.txt
|
||||
$ swarm up --encrypt mytest.txt
|
||||
> <reference hash>
|
||||
|
||||
Then, we draw an EC key pair and use the public key to create the access-controlled manifest.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm access new pk --grant-key <public key> <reference hash>
|
||||
> <reference of access controlled manifest>
|
||||
|
||||
We can retrieve the access-controlled manifest via a node that has the private key. You can add a private key using ``geth`` (see `here <https://github.com/ethereum/go-ethereum/wiki/Managing-your-accounts>`_).
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzaccount <address of node with granted private key> down bzz:/<reference of access controlled manifest> out.txt
|
||||
$ cat out.txt
|
||||
> "testfile"
|
||||
|
||||
2. ``act`` strategy
|
||||
|
||||
We can also supply a list of public keys to create the access-controlled manifest.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm access new act --grant-keys <public key list> <reference hash>
|
||||
> <reference of access controlled manifest>
|
||||
|
||||
Again, only nodes that possess the private key will have access to the content.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzaccount <address of node with a granted private key> down bzz:/<reference of access controlled manifest> out.txt
|
||||
$ cat out.txt
|
||||
> "testfile"
|
203
docs/swarm-guide/contents/usage/bzz.rst
Normal file
@ -0,0 +1,203 @@
|
||||
.. _BZZ URL schemes:
|
||||
|
||||
BZZ URL schemes
|
||||
=======================
|
||||
|
||||
Swarm offers 6 distinct URL schemes:
|
||||
|
||||
bzz
|
||||
-----
|
||||
|
||||
The bzz scheme assumes that the domain part of the url points to a manifest. When retrieving the asset addressed by the URL, the manifest entries are matched against the URL path. The entry with the longest matching path is retrieved and served with the content type specified in the corresponding manifest entry.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
GET http://localhost:8500/bzz:/2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d/readme.md
|
||||
|
||||
returns a readme.md file if the manifest at the given hash address contains such an entry.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ ls
|
||||
readme.md
|
||||
$ swarm --recursive up .
|
||||
c4c81dbce3835846e47a83df549e4cad399c6a81cbf83234274b87d49f5f9020
|
||||
$ curl http://localhost:8500/bzz-raw:/c4c81dbce3835846e47a83df549e4cad399c6a81cbf83234274b87d49f5f9020/readme.md
|
||||
## Hello Swarm!
|
||||
|
||||
Swarm is awesome%
|
||||
|
||||
If the manifest does not contain an file at ``readme.md`` itself, but it does contain multiple entries to which the URL could be resolved, e.g. in the example above, the manifest has entries for ``readme.md.1`` and ``readme.md.2``, the API returns an HTTP response "300 Multiple Choices", indicating that the request could not be unambiguously resolved. A list of available entries is returned via HTTP or JSON.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ ls
|
||||
readme.md.1 readme.md.2
|
||||
$ swarm --recursive up .
|
||||
679bde3ccb6fb911db96a0ea1586c04899c6c0cc6d3426e9ee361137b270a463
|
||||
$ curl -H "Accept:application/json" http://localhost:8500/bzz:/679bde3ccb6fb911db96a0ea1586c04899c6c0cc6d3426e9ee361137b270a463/readme.md
|
||||
{"Msg":"\u003ca href='/bzz:/679bde3ccb6fb911db96a0ea1586c04899c6c0cc6d3426e9ee361137b270a463/readme.md.1'\u003ereadme.md.1\u003c/a\u003e\u003cbr/\u003e\u003ca href='/bzz:/679bde3ccb6fb911db96a0ea1586c04899c6c0cc6d3426e9ee361137b270a463/readme.md.2'\u003ereadme.md.2\u003c/a\u003e\u003cbr/\u003e","Code":300,"Timestamp":"Fri, 15 Jun 2018 14:48:42 CEST","Details":""}
|
||||
$ curl -H "Accept:application/json" http://localhost:8500/bzz:/679bde3ccb6fb911db96a0ea1586c04899c6c0cc6d3426e9ee361137b270a463/readme.md | jq
|
||||
{
|
||||
"Msg": "<a href='/bzz:/679bde3ccb6fb911db96a0ea1586c04899c6c0cc6d3426e9ee361137b270a463/readme.md.1'>readme.md.1</a><br/><a href='/bzz:/679bde3ccb6fb911db96a0ea1586c04899c6c0cc6d3426e9ee361137b270a463/readme.md.2'>readme.md.2</a><br/>",
|
||||
"Code": 300,
|
||||
"Timestamp": "Fri, 15 Jun 2018 14:49:02 CEST",
|
||||
"Details": ""
|
||||
}
|
||||
|
||||
``bzz`` scheme also accepts POST requests to upload content and create manifest for them in one go:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl -H "Content-Type: text/plain" --data-binary "some-data" http://localhost:8500/bzz:/
|
||||
635d13a547d3252839e9e68ac6446b58ae974f4f59648fe063b07c248494c7b2%
|
||||
$ curl http://localhost:8500/bzz:/635d13a547d3252839e9e68ac6446b58ae974f4f59648fe063b07c248494c7b2/
|
||||
some-data%
|
||||
$ curl -H "Accept:application/json" http://localhost:8500/bzz-raw:/635d13a547d3252839e9e68ac6446b58ae974f4f59648fe063b07c248494c7b2/ | jq .
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"hash": "379f234c04ed1a18722e4c76b5029ff6e21867186c4dfc101be4f1dd9a879d98",
|
||||
"contentType": "text/plain",
|
||||
"mode": 420,
|
||||
"size": 9,
|
||||
"mod_time": "2018-06-15T15:46:28.835066044+02:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. _bzz-raw:
|
||||
|
||||
bzz-raw
|
||||
-------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
GET http://localhost:8500/bzz-raw:/2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d
|
||||
|
||||
|
||||
When responding to GET requests with the bzz-raw scheme, Swarm does not assume that the hash resolves to a manifest. Instead it just serves the asset referenced by the hash directly. So if the hash actually resolves to a manifest, it returns the raw manifest content itself.
|
||||
|
||||
E.g. continuing the example in the ``bzz`` section above with ``readme.md.1`` and ``readme.md.2`` in the manifest:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl http://localhost:8500/bzz-raw:/679bde3ccb6fb911db96a0ea1586c04899c6c0cc6d3426e9ee361137b270a463/ | jq
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"hash": "efc6d4a7d7f0846973a321d1702c0c478a20f72519516ef230b63baa3da18c22",
|
||||
"path": "readme.md.",
|
||||
"contentType": "application/bzz-manifest+json",
|
||||
"mod_time": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
$ curl http://localhost:8500/bzz-raw:/efc6d4a7d7f0846973a321d1702c0c478a20f72519516ef230b63baa3da18c22/ | jq
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"hash": "d0675100bc4580a0ad890b5d6f06310c0705d4ab1e796cfa1a8c597840f9793f",
|
||||
"path": "1",
|
||||
"mode": 420,
|
||||
"size": 33,
|
||||
"mod_time": "2018-06-15T14:21:32+02:00"
|
||||
},
|
||||
{
|
||||
"hash": "f97cf36ac0dd7178c098f3661cd0402fcc711ff62b67df9893d29f1db35adac6",
|
||||
"path": "2",
|
||||
"mode": 420,
|
||||
"size": 35,
|
||||
"mod_time": "2018-06-15T14:42:06+02:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
The ``content_type`` query parameter can be supplied to specify the MIME type you are requesting, otherwise content is served as an octet-stream per default. For instance if you have a pdf document (not the manifest wrapping it) at hash ``6a182226...`` then the following url will properly serve it.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
GET http://localhost:8500/bzz-raw:/6a18222637cafb4ce692fa11df886a03e6d5e63432c53cbf7846970aa3e6fdf5?content_type=application/pdf
|
||||
|
||||
``bzz-raw`` also supports POST requests to upload content to Swarm, the response is the hash of the uploaded content:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl --data-binary "some-data" http://localhost:8500/bzz-raw:/
|
||||
379f234c04ed1a18722e4c76b5029ff6e21867186c4dfc101be4f1dd9a879d98%
|
||||
$ curl http://localhost:8500/bzz-raw:/379f234c04ed1a18722e4c76b5029ff6e21867186c4dfc101be4f1dd9a879d98/
|
||||
some-data%
|
||||
|
||||
bzz-list
|
||||
-------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
GET http://localhost:8500/bzz-list:/2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d/path
|
||||
|
||||
Returns a list of all files contained in <manifest> under <path> grouped into common prefixes using ``/`` as a delimiter. If no path is supplied, all files in manifest are returned. The response is a JSON-encoded object with ``common_prefixes`` string field and ``entries`` list field.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl http://localhost:8500/bzz-list:/679bde3ccb6fb911db96a0ea1586c04899c6c0cc6d3426e9ee361137b270a463/ | jq
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"hash": "d0675100bc4580a0ad890b5d6f06310c0705d4ab1e796cfa1a8c597840f9793f",
|
||||
"path": "readme.md.1",
|
||||
"mode": 420,
|
||||
"size": 33,
|
||||
"mod_time": "2018-06-15T14:21:32+02:00"
|
||||
},
|
||||
{
|
||||
"hash": "f97cf36ac0dd7178c098f3661cd0402fcc711ff62b67df9893d29f1db35adac6",
|
||||
"path": "readme.md.2",
|
||||
"mode": 420,
|
||||
"size": 35,
|
||||
"mod_time": "2018-06-15T14:42:06+02:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
bzz-hash
|
||||
-------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
GET http://localhost:8500/bzz-hash:/theswarm.eth/
|
||||
|
||||
Swarm accepts GET requests for bzz-hash url scheme and responds with the hash value of the raw content, the same content returned by requests with bzz-raw scheme. Hash of the manifest is also the hash stored in ENS so bzz-hash can be used for ENS domain resolution.
|
||||
|
||||
Response content type is *text/plain*.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl http://localhost:8500/bzz-hash:/theswarm.eth/
|
||||
7a90587bfc04ac4c64aeb1a96bc84f053d3d84cefc79012c9a07dd5230dc1fa4%
|
||||
|
||||
bzz-immutable
|
||||
-------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
GET http://localhost:8500/bzz-immutable:/2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d
|
||||
|
||||
The same as the generic scheme but there is no ENS domain resolution, the domain part of the path needs to be a valid hash. This is also a read-only scheme but explicit in its integrity protection. A particular bzz-immutable url will always necessarily address the exact same fixed immutable content.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl http://localhost:8500/bzz-immutable:/679bde3ccb6fb911db96a0ea1586c04899c6c0cc6d3426e9ee361137b270a463/readme.md.1
|
||||
## Hello Swarm!
|
||||
|
||||
Swarm is awesome%
|
||||
$ curl -H "Accept:application/json" http://localhost:8500/bzz-immutable:/theswarm.eth/ | jq .
|
||||
{
|
||||
"Msg": "cannot resolve theswarm.eth: immutable address not a content hash: \"theswarm.eth\"",
|
||||
"Code": 404,
|
||||
"Timestamp": "Fri, 15 Jun 2018 13:22:27 UTC",
|
||||
"Details": ""
|
||||
}
|
||||
|
||||
|
53
docs/swarm-guide/contents/usage/encryption.rst
Normal file
@ -0,0 +1,53 @@
|
||||
.. _Encryption:
|
||||
|
||||
Encryption
|
||||
===========
|
||||
|
||||
Introduced in POC 0.3, symmetric encryption is now readily available to be used with the ``swarm up`` upload command.
|
||||
The encryption mechanism is meant to protect your information and make the chunked data unreadable to any handling Swarm node.
|
||||
|
||||
Swarm uses `Counter mode encryption <https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)>`_ to encrypt and decrypt content. When you upload content to Swarm, the uploaded data is split into 4 KB chunks. These chunks will all be encoded with a separate randomly generated encryption key. The encryption happens on your local Swarm node, unencrypted data is not shared with other nodes. The reference of a single chunk (and the whole content) will be the concatenation of the hash of encoded data and the decryption key. This means the reference will be longer than the standard unencrypted Swarm reference (64 bytes instead of 32 bytes).
|
||||
|
||||
When your node syncs the encrypted chunks of your content with other nodes, it does not share the full references (or the decryption keys in any way) with the other nodes. This means that other nodes will not be able to access your original data, moreover they will not be able to detect whether the synchronized chunks are encrypted or not.
|
||||
|
||||
When your data is retrieved it will only get decrypted on your local Swarm node. During the whole retrieval process the chunks traverse the network in their encrypted form, and none of the participating peers are able to decrypt them. They are only decrypted and assembled on the Swarm node you use for the download.
|
||||
|
||||
More info about how we handle encryption at Swarm can be found `here <https://github.com/ethersphere/swarm/wiki/Symmetric-Encryption-for-Swarm-Content>`_.
|
||||
|
||||
.. note::
|
||||
Swarm currently supports both encrypted and unencrypted ``swarm up`` commands through usage of the ``--encrypt`` flag.
|
||||
This might change in the future as we will refine and make Swarm a safer network.
|
||||
|
||||
.. important::
|
||||
The encryption feature is non-deterministic (due to a random key generated on every upload request) and users of the API should not rely on the result being idempotent; thus uploading the same content twice to Swarm with encryption enabled will not result in the same reference.
|
||||
|
||||
|
||||
Example usage:
|
||||
|
||||
First, we create a simple test file.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ echo "testfile" > mytest.txt
|
||||
|
||||
We upload the test file **without** encryption,
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm up mytest.txt
|
||||
> <file reference>
|
||||
|
||||
and **with** encryption.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm up --encrypt mytest.txt
|
||||
> <encrypted reference>
|
||||
|
||||
Note that the reference of the encrypted upload is **longer** than that of the unencrypted upload. Note also that, because of the random encryption key, repeating the encrypted upload results in a different reference:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm up --encrypt mytest.txt
|
||||
<another encrypted reference>
|
||||
|
59
docs/swarm-guide/contents/usage/ens.rst
Normal file
@ -0,0 +1,59 @@
|
||||
.. _Ethereum Name Service:
|
||||
|
||||
Using ENS names
|
||||
================
|
||||
|
||||
.. note:: In order to `resolve` ENS names, your Swarm node has to be connected to an Ethereum blockchain (mainnet, or testnet). See `Getting Started <./gettingstarted.html#connect-ens>`_ for instructions. This section explains how you can register your content to your ENS name.
|
||||
|
||||
`ENS <http://ens.readthedocs.io/en/latest/introduction.html>`_ is the system that Swarm uses to permit content to be referred to by a human-readable name, such as "theswarm.eth". It operates analogously to the DNS system, translating human-readable names into machine identifiers - in this case, the Swarm hash of the content you're referring to. By registering a name and setting it to resolve to the content hash of the root manifest of your site, users can access your site via a URL such as ``bzz://theswarm.eth/``.
|
||||
|
||||
.. note:: Currently The `bzz` scheme is not supported in major browsers such as Chrome, Firefox or Safari. If you want to access the `bzz` scheme through these browsers, currently you have to either use an HTTP gateway, such as https://swarm-gateways.net/bzz:/theswarm.eth/ or use a browser which supports the `bzz` scheme, such as Mist <https://github.com/ethereum/mist>.
|
||||
|
||||
Suppose we upload a directory to Swarm containing (among other things) the file ``example.pdf``.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --recursive up /path/to/dir
|
||||
>2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d
|
||||
|
||||
If we register the root hash as the ``content`` for ``theswarm.eth``, then we can access the pdf at
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
bzz://theswarm.eth/example.pdf
|
||||
|
||||
if we are using a Swarm-enabled browser, or at
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
http://localhost:8500/bzz:/theswarm.eth/example.pdf
|
||||
|
||||
via a local gateway. We will get served the same content as with:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
http://localhost:8500/bzz:/2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d/example.pdf
|
||||
|
||||
Please refer to the `official ENS documentation <http://ens.readthedocs.io/en/latest/introduction.html>`_ for the full details on how to register content hashes to ENS.
|
||||
|
||||
In short, the steps you must take are:
|
||||
|
||||
1. Register an ENS name.
|
||||
2. Associate a resolver with that name.
|
||||
3. Register the Swarm hash with the resolver as the ``content``.
|
||||
|
||||
We recommend using https://manager.ens.domains/. This will make it easy for you to:
|
||||
|
||||
- Associate the default resolver with your name
|
||||
- Register a Swarm hash.
|
||||
|
||||
.. note:: When you register a Swarm hash with https://manager.ens.domains/ you MUST prefix the hash with 0x. For example 0x2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d
|
||||
|
||||
Overview of ENS (video)
|
||||
-----------------------
|
||||
|
||||
Nick Johnson on the Ethereum Name System
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/pLDDbCZXvTE" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
445
docs/swarm-guide/contents/usage/feed.rst
Normal file
@ -0,0 +1,445 @@
|
||||
Feeds
|
||||
========================
|
||||
|
||||
.. note::
|
||||
Feeds, previously known as *Mutable Resource Updates*, is an experimental feature, available since Swarm POC3. It is under active development, so expect things to change.
|
||||
|
||||
Since Swarm hashes are content addressed, changes to data will constantly result in changing hashes. Swarm Feeds provide a way to easily overcome this problem and provide a single, persistent, identifier to follow sequential data.
|
||||
|
||||
The usual way of keeping the same pointer to changing data is using the Ethereum Name Service (ENS). However, since ENS is an on-chain feature, it might not be suitable for each use case since:
|
||||
|
||||
1. Every update to an ENS resolver will cost gas to execute
|
||||
2. It is not be possible to change the data faster than the rate that new blocks are mined
|
||||
3. ENS resolution requires your node to be synced to the blockchain
|
||||
|
||||
|
||||
Swarm Feeds provide a way to have a persistent identifier for changing data without having to use ENS. It is named Feeds for its similarity with a news feed.
|
||||
|
||||
If you are using *Feeds* in conjunction with an ENS resolver contract, only one initial transaction to register the "Feed manifest address" will be necessary. This key will resolve to the latest version of the Feed (updating the Feed will not change the key).
|
||||
|
||||
You can think of a Feed as a user's Twitter account, where he/she posts updates about a particular Topic. In fact, the Feed object is simply defined as:
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
type Feed struct {
|
||||
Topic Topic
|
||||
User common.Address
|
||||
}
|
||||
|
||||
That is, a specific user posting updates about a specific Topic.
|
||||
|
||||
Users can post to any topic. If you know the user's address and agree on a particular Topic, you can then effectively "follow" that user's Feed.
|
||||
|
||||
.. important::
|
||||
How you build the Topic is entirely up to your application. You could calculate a hash of something and use that, the recommendation
|
||||
is that it should be easy to derive out of information that is accesible to other users.
|
||||
|
||||
For convenience, ``feed.NewTopic()`` provides a way to "merge" a byte array with a string in order to build a Feed Topic out of both.
|
||||
This is used at the API level to create the illusion of subtopics. This way of building topics allows using a random byte array (for example the hash of a photo)
|
||||
and merge it with a human-readable string such as "comments" in order to create a Topic that could represent the comments about that particular photo.
|
||||
This way, when you see a picture in a website you could immediately build a Topic out of it and see if some user posted comments about that photo.
|
||||
|
||||
Feeds are not created, only updated. If a particular Feed (user, topic combination) has never posted to, trying to fetch updates will yield nothing.
|
||||
|
||||
Feed Manifests
|
||||
--------------
|
||||
|
||||
A Feed Manifest is simply a JSON object that contains the ``Topic`` and ``User`` of a particular Feed (i.e., a serialized ``Feed`` object). Uploading this JSON object to Swarm in the regular way will return the immutable hash of this object. We can then store this immutable hash in an ENS Resolver so that we can have a ENS domain that "follows" the Feed described in the manifest.
|
||||
|
||||
Feeds API
|
||||
---------
|
||||
|
||||
There are 3 different ways of interacting with *Feeds* : HTTP API, CLI and Golang API.
|
||||
|
||||
HTTP API
|
||||
~~~~~~~~
|
||||
|
||||
Posting to a Feed
|
||||
.................
|
||||
|
||||
Since Feed updates need to be signed, and an update has some correlation with a previous update, it is necessary to retrieve first the Feed's current status. Thus, the first step to post an update will be to retrieve this current status in a ready-to-sign template:
|
||||
|
||||
1. Get Feed template
|
||||
|
||||
``GET /bzz-feed:/?topic=<TOPIC>&user=<USER>&meta=1``
|
||||
|
||||
``GET /bzz-feed:/<MANIFEST OR ENS NAME>/?meta=1``
|
||||
|
||||
|
||||
Where:
|
||||
+ ``user``: Ethereum address of the user who publishes the Feed
|
||||
+ ``topic``: Feed topic, encoded as a hex string. Topic is an arbitrary 32-byte string (64 hex chars)
|
||||
|
||||
.. note::
|
||||
+ If ``topic`` is omitted, it is assumed to be zero, 0x000...
|
||||
+ if ``name=<name>`` (optional) is provided, a subtopic is composed with that name
|
||||
+ A common use is to omit topic and just use ``name``, allowing for human-readable topics
|
||||
|
||||
You will receive a JSON like the below:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
{
|
||||
"feed": {
|
||||
"topic": "0x6a61766900000000000000000000000000000000000000000000000000000000",
|
||||
"user": "0xdfa2db618eacbfe84e94a71dda2492240993c45b"
|
||||
},
|
||||
"epoch": {
|
||||
"level": 16,
|
||||
"time": 1534237239
|
||||
}
|
||||
"protocolVersion" : 0,
|
||||
}
|
||||
|
||||
2. Post the update
|
||||
|
||||
Extract the fields out of the JSON and build a query string as below:
|
||||
|
||||
``POST /bzz-feed:/?topic=<TOPIC>&user=<USER>&level=<LEVEL>&time=<TIME>&signature=<SIGNATURE>``
|
||||
|
||||
Where:
|
||||
+ ``topic``: Feed topic, as specified above
|
||||
+ ``user``: your Ethereum address
|
||||
+ ``level``: Suggested frequency level retrieved in the JSON above
|
||||
+ ``time``: Suggested timestamp retrieved in the JSON above
|
||||
+ ``protocolVersion``: Feeds protocol version. Currently ``0``
|
||||
+ ``signature``: Signature, hex encoded. See below on how to calclulate the signature
|
||||
+ Request posted data: binary stream with the update data
|
||||
|
||||
|
||||
Reading a Feed
|
||||
..............
|
||||
|
||||
To retrieve a Feed's last update:
|
||||
|
||||
``GET /bzz-feed:/?topic=<TOPIC>&user=<USER>``
|
||||
|
||||
``GET /bzz-feed:/<MANIFEST OR ENS NAME>``
|
||||
|
||||
.. note::
|
||||
|
||||
+ Again, if ``topic`` is omitted, it is assumed to be zero, 0x000...
|
||||
+ If ``name=<name>`` is provided, a subtopic is composed with that name
|
||||
+ A common use is to omit ``topic`` and just use ``name``, allowing for human-readable topics, for example:
|
||||
``GET /bzz-feed:/?name=profile-picture&user=<USER>``
|
||||
|
||||
|
||||
To get a previous update:
|
||||
|
||||
Add an addtional ``time`` parameter. The last update before that ``time`` (unix time) will be looked up.
|
||||
|
||||
``GET /bzz-feed:/?topic=<TOPIC>&user=<USER>&time=<T>``
|
||||
|
||||
``GET /bzz-feed:/<MANIFEST OR ENS NAME>?time=<T>``
|
||||
|
||||
Creating a Feed Manifest
|
||||
........................
|
||||
|
||||
To create a ``Feed manifest`` using the HTTP API:
|
||||
|
||||
``POST /bzz-feed:/?topic=<TOPIC>&user=<USER>&manifest=1.`` With an empty body.
|
||||
|
||||
This will create a manifest referencing the provided Feed.
|
||||
|
||||
.. note::
|
||||
This API call will be deprecated in the near future.
|
||||
|
||||
Go API
|
||||
~~~~~~~~
|
||||
|
||||
Query object
|
||||
.................
|
||||
|
||||
The ``Query`` object allows you to build a query to browse a particular ``Feed``.
|
||||
|
||||
The default ``Query``, obtained with ``feed.NewQueryLatest()`` will build a ``Query`` that retrieves the latest update of the given ``Feed``.
|
||||
|
||||
You can also use ``feed.NewQuery()`` instead, if you want to build a ``Query`` to look up an update before a certain date.
|
||||
|
||||
Advanced usage of ``Query`` includes hinting the lookup algorithm for faster lookups. The default hint ``lookup.NoClue`` will have your node track Feeds you query frequently and handle hints automatically.
|
||||
|
||||
Request object
|
||||
.................
|
||||
|
||||
The ``Request`` object makes it easy to construct and sign a request to Swarm to update a particular Feed. It contains methods to sign and add data. We can manually build the ``Request`` object, or fetch a valid "template" to use for the update.
|
||||
|
||||
A ``Request`` can also be serialized to JSON in case you need your application to delegate signatures, such as having a browser sign a Feed update request.
|
||||
|
||||
Posting to a Feed
|
||||
.................
|
||||
|
||||
1. Retrieve a ``Request`` object or build one from scratch. To retrieve a ready-to-sign one:
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func (c *Client) GetFeedRequest(query *feed.Query, manifestAddressOrDomain string) (*feed.Request, error)
|
||||
|
||||
2. Use ``Request.SetData()`` and ``Request.Sign()`` to load the payload data into the request and sign it
|
||||
|
||||
3. Call ``UpdateFeed()`` with the filled ``Request``:
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func (c *Client) UpdateFeed(request *feed.Request, createManifest bool) (io.ReadCloser, error)
|
||||
|
||||
Reading a Feed
|
||||
..............
|
||||
|
||||
To retrieve a Feed update, use `client.QueryFeed()`. ``QueryFeed`` returns a byte stream with the raw content of the Feed update.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func (c *Client) QueryFeed(query *feed.Query, manifestAddressOrDomain string) (io.ReadCloser, error)
|
||||
|
||||
``manifestAddressOrDomain`` is the address you obtained in ``CreateFeedWithManifest`` or an ``ENS`` domain whose Resolver
|
||||
points to that address.
|
||||
``query`` is a Query object, as defined above.
|
||||
|
||||
You only need to provide either ``manifestAddressOrDomain`` or ``Query`` to ``QueryFeed()``. Set to ``""`` or ``nil`` respectively.
|
||||
|
||||
Creating a Feed Manifest
|
||||
........................
|
||||
|
||||
Swarm client (package swarm/api/client) has the following method:
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
func (c *Client) CreateFeedWithManifest(request *feed.Request) (string, error)
|
||||
|
||||
``CreateFeedWithManifest`` uses the ``request`` parameter to set and create a ``Feed manifest``.
|
||||
|
||||
Returns the resulting ``Feed manifest address`` that you can set in an ENS Resolver (setContent) or reference future updates using ``Client.UpdateFeed()``
|
||||
|
||||
Example Go code
|
||||
...............
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
// Build a `Feed` object to track a particular user's updates
|
||||
f := new(feed.Feed)
|
||||
f.User = signer.Address()
|
||||
f.Topic, _ = feed.NewTopic("weather",nil)
|
||||
|
||||
// Build a `Query` to retrieve a current Request for this feed
|
||||
query := feeds.NewQueryLatest(&f, lookup.NoClue)
|
||||
|
||||
// Retrieve a ready-to-sign request using our query
|
||||
// (queries can be reused)
|
||||
request, err := client.GetFeedRequest(query, "")
|
||||
if err != nil {
|
||||
utils.Fatalf("Error retrieving feed status: %s", err.Error())
|
||||
}
|
||||
|
||||
// set the new data
|
||||
request.SetData([]byte("Weather looks bright and sunny today, we should merge this PR and go out enjoy"))
|
||||
|
||||
// sign update
|
||||
if err = request.Sign(signer); err != nil {
|
||||
utils.Fatalf("Error signing feed update: %s", err.Error())
|
||||
}
|
||||
|
||||
// post update
|
||||
err = client.UpdateFeed(request)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error updating feed: %s", err.Error())
|
||||
}
|
||||
|
||||
Command-Line
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The CLI API allows us to go through how Feeds work using practical examples. You can look up CL usage by typing ``swarm feed`` into your CLI.
|
||||
|
||||
In the CLI examples, we will create and update feeds using the bzzapi on a running local Swarm node that listens by default on port 8500.
|
||||
|
||||
Creating a Feed Manifest
|
||||
........................
|
||||
|
||||
The Swarm CLI allows creating Feed Manifests directly from the console.
|
||||
|
||||
``swarm feed create`` is defined as a command to create and publish a ``Feed manifest``.
|
||||
|
||||
The feed topic can be built in the following ways:
|
||||
* use ``--topic`` to set the topic to an arbitrary binary hex string.
|
||||
* use ``--name`` to set the topic to a human-readable name.
|
||||
For example, ``--name`` could be set to "profile-picture", meaning this feed allows to get this user's current profile picture.
|
||||
* use both ``--topic`` and ``--name`` to create named subtopics.
|
||||
For example, `--topic` could be set to an Ethereum contract address and ``--name`` could be set to "comments", meaning this feed tracks a discussion about that contract.
|
||||
|
||||
The ``--user`` flag allows to have this manifest refer to a user other than yourself. If not specified, it will then default to your local account (``--bzzaccount``).
|
||||
|
||||
If you don't specify a name or a topic, the topic will be set to ``0 hex`` and name will be set to your username.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzapi http://localhost:8500 feed create --name test
|
||||
|
||||
creates a feed named "test". This is equivalent to the HTTP API way of
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzapi http://localhost:8500 feed create --topic 0x74657374
|
||||
|
||||
since ``test string == 0x74657374 hex``. Name and topic are interchangeable, as long as you don't specify both.
|
||||
|
||||
``feed create`` will return the **feed manifest**.
|
||||
|
||||
You can also use ``curl`` in the HTTP API, but, here, you have to explicitly define the user (which, in this case, is your account) and the manifest.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl -XPOST -d 'name=test&user=<your account>&manifest=1' http://localhost:8500/bzz-Feed:/
|
||||
|
||||
is equivalent to
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl -XPOST -d 'topic=0x74657374&user=<your account>&manifest=1' http://localhost:8500/bzz-Feed:/
|
||||
|
||||
|
||||
Posting to a Feed
|
||||
.................
|
||||
|
||||
To update a Feed with the CLI, use ``feed update``. The **update** argument has to be in ``hex``. If you want to update your *test* feed with the update *hello*, you can refer to it by name:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzapi http://localhost:8500 feed update --name test 0x68656c6c6f203
|
||||
|
||||
You can also refer to it by topic,
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzapi http://localhost:8500 feed update --topic 0x74657374 0x68656c6c6f203
|
||||
|
||||
or manifest.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzapi http://localhost:8500 feed update --manifest <manifest hash> 0x68656c6c6f203
|
||||
|
||||
Reading Feed status
|
||||
...................
|
||||
|
||||
You can read the feed object using ``feed info``. Again, you can use the feed name, the topic, or the manifest hash. Below, we use the name.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --bzzapi http://localhost:8500 feed info --name test
|
||||
|
||||
Reading Feed Updates
|
||||
.....................
|
||||
|
||||
Although the Swarm CLI doesn't have the functionality to retrieve feed updates, we can use ``curl`` and the HTTP api to retrieve them. Again, you can use the feed name, topic, or manifest hash. To return the update ``hello`` for your ``test`` feed, do this:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ curl 'http://localhost:8500/bzz-feed:/?user=<your address>&name=test'
|
||||
|
||||
|
||||
Computing Feed Signatures
|
||||
-------------------------
|
||||
|
||||
1. computing the digest:
|
||||
|
||||
The digest is computed concatenating the following:
|
||||
+ 1-byte protocol version (currently 0)
|
||||
+ 7-bytes padding, set to 0
|
||||
+ 32-bytes topic
|
||||
+ 20-bytes user address
|
||||
+ 7-bytes time, little endian
|
||||
+ 1-byte level
|
||||
+ payload data (variable length)
|
||||
|
||||
2. Take the SHA3 hash of the above digest
|
||||
|
||||
3. Compute the ECDSA signature of the hash
|
||||
|
||||
4. Convert to hex string and put in the ``signature`` field above
|
||||
|
||||
JavaScript example
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var web3 = require("web3");
|
||||
|
||||
if (module !== undefined) {
|
||||
module.exports = {
|
||||
digest: feedUpdateDigest
|
||||
}
|
||||
}
|
||||
|
||||
var topicLength = 32;
|
||||
var userLength = 20;
|
||||
var timeLength = 7;
|
||||
var levelLength = 1;
|
||||
var headerLength = 8;
|
||||
var updateMinLength = topicLength + userLength + timeLength + levelLength + headerLength;
|
||||
|
||||
|
||||
|
||||
|
||||
function feedUpdateDigest(request /*request*/, data /*UInt8Array*/) {
|
||||
var topicBytes = undefined;
|
||||
var userBytes = undefined;
|
||||
var protocolVersion = 0;
|
||||
|
||||
protocolVersion = request.protocolVersion
|
||||
|
||||
try {
|
||||
topicBytes = web3.utils.hexToBytes(request.feed.topic);
|
||||
} catch(err) {
|
||||
console.error("topicBytes: " + err);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
userBytes = web3.utils.hexToBytes(request.feed.user);
|
||||
} catch(err) {
|
||||
console.error("topicBytes: " + err);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var buf = new ArrayBuffer(updateMinLength + data.length);
|
||||
var view = new DataView(buf);
|
||||
var cursor = 0;
|
||||
|
||||
view.setUint8(cursor, protocolVersion) // first byte is protocol version.
|
||||
cursor+=headerLength; // leave the next 7 bytes (padding) set to zero
|
||||
|
||||
topicBytes.forEach(function(v) {
|
||||
view.setUint8(cursor, v);
|
||||
cursor++;
|
||||
});
|
||||
|
||||
userBytes.forEach(function(v) {
|
||||
view.setUint8(cursor, v);
|
||||
cursor++;
|
||||
});
|
||||
|
||||
// time is little-endian
|
||||
view.setUint32(cursor, request.epoch.time, true);
|
||||
cursor += 7;
|
||||
|
||||
view.setUint8(cursor, request.epoch.level);
|
||||
cursor++;
|
||||
|
||||
data.forEach(function(v) {
|
||||
view.setUint8(cursor, v);
|
||||
cursor++;
|
||||
});
|
||||
console.log(web3.utils.bytesToHex(new Uint8Array(buf)))
|
||||
|
||||
return web3.utils.sha3(web3.utils.bytesToHex(new Uint8Array(buf)));
|
||||
}
|
||||
|
||||
// data payload
|
||||
data = new Uint8Array([5,154,15,165,62])
|
||||
|
||||
// request template, obtained calling http://localhost:8500/bzz-feed:/?user=<0xUSER>&topic=<0xTOPIC>&meta=1
|
||||
request = {"feed":{"topic":"0x1234123412341234123412341234123412341234123412341234123412341234","user":"0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"},"epoch":{"time":1538650124,"level":25},"protocolVersion":0}
|
||||
|
||||
// obtain digest
|
||||
digest = feedUpdateDigest(request, data)
|
||||
|
||||
console.log(digest)
|
112
docs/swarm-guide/contents/usage/fuse.rst
Normal file
@ -0,0 +1,112 @@
|
||||
|
||||
FUSE
|
||||
======================
|
||||
|
||||
|
||||
Another way of interacting with Swarm is by mounting it as a local filesystem using `FUSE <https://en.wikipedia.org/wiki/Filesystem_in_Userspace>`_ (Filesystem in Userspace). There are three IPC API's which help in doing this.
|
||||
|
||||
.. note:: FUSE needs to be installed on your Operating System for these commands to work. Windows is not supported by FUSE, so these command will work only in Linux, Mac OS and FreeBSD. For installation instruction for your OS, see "Installing FUSE" section below.
|
||||
|
||||
|
||||
Installing FUSE
|
||||
----------------
|
||||
|
||||
1. Linux (Ubuntu)
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ sudo apt-get install fuse
|
||||
$ sudo modprobe fuse
|
||||
$ sudo chown <username>:<groupname> /etc/fuse.conf
|
||||
$ sudo chown <username>:<groupname> /dev/fuse
|
||||
|
||||
2. Mac OS
|
||||
|
||||
Either install the latest package from https://osxfuse.github.io/ or use brew as below
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ brew update
|
||||
$ brew install caskroom/cask/brew-cask
|
||||
$ brew cask install osxfuse
|
||||
|
||||
|
||||
CLI Usage
|
||||
-----------
|
||||
|
||||
The Swarm CLI now integrates commands to make FUSE usage easier and streamlined.
|
||||
|
||||
.. note:: When using FUSE from the CLI, we assume you are running a local Swarm node on your machine. The FUSE commands attach to the running node through `bzzd.ipc`
|
||||
|
||||
Mount
|
||||
^^^^^^^^
|
||||
|
||||
One use case to mount a Swarm hash via FUSE is a file sharing feature accessible via your local file system.
|
||||
Files uploaded to Swarm are then transparently accessible via your local file system, just as if they were stored locally.
|
||||
|
||||
To mount a Swarm resource, first upload some content to Swarm using the ``swarm up <resource>`` command.
|
||||
You can also upload a complete folder using ``swarm --recursive up <directory>``.
|
||||
Once you get the returned manifest hash, use it to mount the manifest to a mount point
|
||||
(the mount point should exist on your hard drive):
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm fs mount <manifest-hash> <mount-point>
|
||||
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm fs mount <manifest-hash> /home/user/swarmmount
|
||||
|
||||
|
||||
Your running Swarm node terminal output should show something similar to the following in case the command returned successfuly:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
Attempting to mount /path/to/mount/point
|
||||
Serving 6e4642148d0a1ea60e36931513f3ed6daf3deb5e499dcf256fa629fbc22cf247 at /path/to/mount/point
|
||||
Now serving swarm FUSE FS manifest=6e4642148d0a1ea60e36931513f3ed6daf3deb5e499dcf256fa629fbc22cf247 mountpoint=/path/to/mount/point
|
||||
|
||||
You may get a "Fatal: had an error calling the RPC endpoint while mounting: context deadline exceeded" error if it takes too long to retrieve the content.
|
||||
|
||||
In your OS, via terminal or file browser, you now should be able to access the contents of the Swarm hash at ``/path/to/mount/point``, i.e. ``ls /home/user/swarmmount``
|
||||
|
||||
|
||||
Access
|
||||
^^^^^^^^
|
||||
Through your terminal or file browser, you can interact with your new mount as if it was a local directory. Thus you can add, remove, edit, create files and directories just as on a local directory. Every such action will interact with Swarm, taking effect on the Swarm distributed storage. Every such action also will result **in a new hash** for your mounted directory. If you would unmount and remount the same directory with the previous hash, your changes would seem to have been lost (effectively you are just mounting the previous version). While you change the current mount, this happens under the hood and your mount remains up-to-date.
|
||||
|
||||
Unmount
|
||||
^^^^^^^^
|
||||
To unmount a ``swarmfs`` mount, either use the List Mounts command below, or use a known mount point:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm fs unmount <mount-point>
|
||||
> 41e422e6daf2f4b32cd59dc6a296cce2f8cce1de9f7c7172e9d0fc4c68a3987a
|
||||
|
||||
The returned hash is the latest manifest version that was mounted.
|
||||
You can use this hash to remount the latest version with the most recent changes.
|
||||
|
||||
|
||||
List Mounts
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
To see all existing swarmfs mount points, use the List Mounts command:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm fs list
|
||||
|
||||
|
||||
Example Output:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
Found 1 swarmfs mount(s):
|
||||
0:
|
||||
Mount point: /path/to/mount/point
|
||||
Latest Manifest: 6e4642148d0a1ea60e36931513f3ed6daf3deb5e499dcf256fa629fbc22cf247
|
||||
Start Manifest: 6e4642148d0a1ea60e36931513f3ed6daf3deb5e499dcf256fa629fbc22cf247
|
||||
|
175
docs/swarm-guide/contents/usage/manifests.rst
Normal file
@ -0,0 +1,175 @@
|
||||
Manifests
|
||||
===============
|
||||
|
||||
.. _swarm-manifest:
|
||||
|
||||
In general manifests declare a list of strings associated with Swarm hashes. A manifest matches to exactly one hash, and it consists of a list of entries declaring the content which can be retrieved through that hash. This is demonstrated by the following example:
|
||||
|
||||
Let's create a directory containing the two orange papers and an html index file listing the two pdf documents.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ ls -1 orange-papers/
|
||||
index.html
|
||||
smash.pdf
|
||||
sw^3.pdf
|
||||
|
||||
$ cat orange-papers/index.html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="./sw^3.pdf">Viktor Trón, Aron Fischer, Dániel Nagy A and Zsolt Felföldi, Nick Johnson: swap, swear and swindle: incentive system for swarm.</a> May 2016
|
||||
</li>
|
||||
<li>
|
||||
<a href="./smash.pdf">Viktor Trón, Aron Fischer, Nick Johnson: smash-proof: auditable storage for swarm secured by masked audit secret hash.</a> May 2016
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
We now use the ``swarm up`` command to upload the directory to Swarm to create a mini virtual site.
|
||||
|
||||
.. note::
|
||||
In this example we are using the public gateway through the `bzz-api` option in order to upload. The examples below assume a node running on localhost to access content. Make sure to run a local node to reproduce these examples.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ swarm --recursive --defaultpath orange-papers/index.html --bzzapi http://swarm-gateways.net/ up orange-papers/ 2> up.log
|
||||
> 2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d
|
||||
|
||||
The returned hash is the hash of the manifest for the uploaded content (the orange-papers directory):
|
||||
|
||||
We now can get the manifest itself directly (instead of the files they refer to) by using the bzz-raw protocol ``bzz-raw``:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ wget -O- "http://localhost:8500/bzz-raw:/2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d"
|
||||
|
||||
> {
|
||||
"entries": [
|
||||
{
|
||||
"hash": "4b3a73e43ae5481960a5296a08aaae9cf466c9d5427e1eaa3b15f600373a048d",
|
||||
"contentType": "text/html; charset=utf-8"
|
||||
},
|
||||
{
|
||||
"hash": "4b3a73e43ae5481960a5296a08aaae9cf466c9d5427e1eaa3b15f600373a048d",
|
||||
"contentType": "text/html; charset=utf-8",
|
||||
"path": "index.html"
|
||||
},
|
||||
{
|
||||
"hash": "69b0a42a93825ac0407a8b0f47ccdd7655c569e80e92f3e9c63c28645df3e039",
|
||||
"contentType": "application/pdf",
|
||||
"path": "smash.pdf"
|
||||
},
|
||||
{
|
||||
"hash": "6a18222637cafb4ce692fa11df886a03e6d5e63432c53cbf7846970aa3e6fdf5",
|
||||
"contentType": "application/pdf",
|
||||
"path": "sw^3.pdf"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. note::
|
||||
macOS users can install wget via homebrew (or use curl).
|
||||
|
||||
|
||||
Manifests contain content_type information for the hashes they reference. In other contexts, where content_type is not supplied or, when you suspect the information is wrong, it is possible to specify the content_type manually in the search query. For example, the manifest itself should be `text/plain`:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
http://localhost:8500/bzz-raw:/2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d?content_type="text/plain"
|
||||
|
||||
Now you can also check that the manifest hash matches the content (in fact, Swarm does this for you):
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ wget -O- http://localhost:8500/bzz-raw:/2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d?content_type="text/plain" > manifest.json
|
||||
|
||||
$ swarm hash manifest.json
|
||||
> 2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d
|
||||
|
||||
|
||||
Path Matching
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
A useful feature of manifests is that we can match paths with URLs.
|
||||
In some sense this makes the manifest a routing table and so the manifest acts as if it was a host.
|
||||
|
||||
More concretely, continuing in our example, when we request:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
GET http://localhost:8500/bzz:/2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d/sw^3.pdf
|
||||
|
||||
Swarm first retrieves the document matching the manifest above. The url path ``sw^3`` is then matched against the entries. In this case a perfect match is found and the document at 6a182226... is served as a pdf.
|
||||
|
||||
As you can see the manifest contains 4 entries, although our directory contained only 3. The extra entry is there because of the ``--defaultpath orange-papers/index.html`` option to ``swarm up``, which associates the empty path with the file you give as its argument. This makes it possible to have a default page served when the url path is empty.
|
||||
This feature essentially implements the most common webserver rewrite rules used to set the landing page of a site served when the url only contains the domain. So when you request
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
GET http://localhost:8500/bzz:/2477cc8584cc61091b5cc084cdcdb45bf3c6210c263b0143f030cf7d750e894d/
|
||||
|
||||
you get served the index page (with content type ``text/html``) at ``4b3a73e43ae5481960a5296a08aaae9cf466c9d5427e1eaa3b15f600373a048d``.
|
||||
|
||||
Paths and directories
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Swarm manifests don't "break" like a file system. In a file system, the directory matches at the path separator (`/` in linux) at the end of a directory name:
|
||||
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
-- dirname/
|
||||
----subdir1/
|
||||
------subdir1file.ext
|
||||
------subdir2file.ext
|
||||
----subdir2/
|
||||
------subdir2file.ext
|
||||
|
||||
In Swarm, path matching does not happen on a given path separator, but **on common prefixes**. Let's look at an example:
|
||||
The current manifest for the ``theswarm.eth`` homepage is as follows:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
wget -O- "http://swarm-gateways.net/bzz-raw:/theswarm.eth/ > manifest.json
|
||||
|
||||
> {"entries":[{"hash":"ee55bc6844189299a44e4c06a4b7fbb6d66c90004159c67e6c6d010663233e26","path":"LICENSE","mode":420,"size":1211,"mod_time":"2018-06-12T15:36:29Z"},
|
||||
{"hash":"57fc80622275037baf4a620548ba82b284845b8862844c3f56825ae160051446","path":"README.md","mode":420,"size":96,"mod_time":"2018-06-12T15:36:29Z"},
|
||||
{"hash":"8919df964703ccc81de5aba1b688ff1a8439b4460440a64940a11e1345e453b5","path":"Swarm_files/","contentType":"application/bzz-manifest+json","mod_time":"0001-01-01T00:00:00Z"},
|
||||
{"hash":"acce5ad5180764f1fb6ae832b624f1efa6c1de9b4c77b2e6ec39f627eb2fe82c","path":"css/","contentType":"application/bzz-manifest+json","mod_time":"0001-01-01T00:00:00Z"},
|
||||
{"hash":"0a000783e31fcf0d1b01ac7d7dae0449cf09ea41731c16dc6cd15d167030a542","path":"ethersphere/orange-papers/","contentType":"application/bzz-manifest+json","mod_time":"0001-01-01T00:00:00Z"},
|
||||
{"hash":"b17868f9e5a3bf94f955780e161c07b8cd95cfd0203d2d731146746f56256e56","path":"f","contentType":"application/bzz-manifest+json","mod_time":"0001-01-01T00:00:00Z"},
|
||||
{"hash":"977055b5f06a05a8827fb42fe6d8ec97e5d7fc5a86488814a8ce89a6a10994c3","path":"i","contentType":"application/bzz-manifest+json","mod_time":"0001-01-01T00:00:00Z"},
|
||||
{"hash":"48d9624942e927d660720109b32a17f8e0400d5096c6d988429b15099e199288","path":"js/","contentType":"application/bzz-manifest+json","mod_time":"0001-01-01T00:00:00Z"},
|
||||
{"hash":"294830cee1d3e63341e4b34e5ec00707e891c9e71f619bc60c6a89d1a93a8f81","path":"talks/","contentType":"application/bzz-manifest+json","mod_time":"0001-01-01T00:00:00Z"},
|
||||
{"hash":"12e1beb28d86ed828f9c38f064402e4fac9ca7b56dab9cf59103268a62a2b35f","contentType":"text/html; charset=utf-8","mode":420,"size":31371,"mod_time":"2018-06-12T15:36:29Z"}
|
||||
]}
|
||||
|
||||
|
||||
Note the ``path`` for entry ``b17868...``: It is ``f``. This means, there are more than one entries for this manifest which start with an `f`, and all those entries will be retrieved by requesting the hash ``b17868...`` and through that arrive at the matching manifest entry:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ wget -O- http://localhost:8500/bzz-raw:/b17868f9e5a3bf94f955780e161c07b8cd95cfd0203d2d731146746f56256e56/
|
||||
|
||||
{"entries":[{"hash":"25e7859eeb7366849f3a57bb100ff9b3582caa2021f0f55fb8fce9533b6aa810","path":"avicon.ico","mode":493,"size":32038,"mod_time":"2018-06-12T15:36:29Z"},
|
||||
{"hash":"97cfd23f9e36ca07b02e92dc70de379a49be654c7ed20b3b6b793516c62a1a03","path":"onts/glyphicons-halflings-regular.","contentType":"application/bzz-manifest+json","mod_time":"0001-01-01T00:00:00Z"}
|
||||
]}
|
||||
|
||||
So we can see that the ``f`` entry in the root hash resolves to a manifest containing ``avicon.ico`` and ``onts/glyphicons-halflings-regular``. The latter is interesting in itself: its ``content_type`` is ``application/bzz-manifest+json``, so it points to another manifest. Its ``path`` also does contain a path separator, but that does not result in a new manifest after the path separator like a directory (e.g. at ``onts/``). The reason is that on the file system on the hard disk, the ``fonts`` directory only contains *one* directory named ``glyphicons-halflings-regular``, thus creating a new manifest for just ``onts/`` would result in an unnecessary lookup. This general approach has been chosen to limit unnecessary lookups that would only slow down retrieval, and manifest "forks" happen in order to have the logarythmic bandwidth needed to retrieve a file in a directory with thousands of files.
|
||||
|
||||
When requesting ``wget -O- "http://swarm-gateways.net/bzz-raw:/theswarm.eth/favicon.ico``, Swarm will first retrieve the manifest at the root hash, match on the first ``f`` in the entry list, resolve the hash for that entry and finally resolve the hash for the ``favicon.ico`` file.
|
||||
|
||||
For the ``theswarm.eth`` page, the same applies to the ``i`` entry in the root hash manifest. If we look up that hash, we'll find entries for ``mages/`` (a further manifest), and ``ndex.html``, whose hash resolves to the main ``index.html`` for the web page.
|
||||
|
||||
Paths like ``css/`` or ``js/`` get their own manifests, just like common directories, because they contain several files.
|
||||
|
||||
.. note::
|
||||
If a request is issued which Swarm can not resolve unambiguosly, a ``300 "Multiplce Choices"`` HTTP status will be returned.
|
||||
In the example above, this would apply for a request for ``http://swarm-gateways.net/bzz:/theswarm.eth/i``, as it could match both ``images/`` as well as ``index.html``
|
BIN
docs/swarm-guide/img-src/dapp-page.odg
Normal file
BIN
docs/swarm-guide/img-src/distance.odg
Normal file
BIN
docs/swarm-guide/img-src/dpa-chunking.odg
Normal file
BIN
docs/swarm-guide/img-src/high-level-components.odg
Normal file
BIN
docs/swarm-guide/img-src/kademlia.odg
Normal file
BIN
docs/swarm-guide/img-src/storage-layer.odg
Normal file
BIN
docs/swarm-guide/img-src/swarm-intro.odg
Normal file
BIN
docs/swarm-guide/img-src/syncing-high-level.odg
Normal file
BIN
docs/swarm-guide/img-src/topology.odg
Normal file
3
docs/swarm-guide/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
Sphinx==1.8.2
|
||||
sphinx-rtd-theme==0.4.2
|
||||
sphinx-tabs==1.1.10
|
@ -1,336 +0,0 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethersphere/swarm/storage"
|
||||
"github.com/ethersphere/swarm/tracing"
|
||||
olog "github.com/opentracing/opentracing-go/log"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSearchTimeout = 1 * time.Second
|
||||
// maximum number of forwarded requests (hops), to make sure requests are not
|
||||
// forwarded forever in peer loops
|
||||
maxHopCount uint8 = 20
|
||||
)
|
||||
|
||||
// Time to consider peer to be skipped.
|
||||
// Also used in stream delivery.
|
||||
var RequestTimeout = 10 * time.Second
|
||||
|
||||
type RequestFunc func(context.Context, *Request) (*enode.ID, chan struct{}, error)
|
||||
|
||||
// Fetcher is created when a chunk is not found locally. It starts a request handler loop once and
|
||||
// keeps it alive until all active requests are completed. This can happen:
|
||||
// 1. either because the chunk is delivered
|
||||
// 2. or because the requester cancelled/timed out
|
||||
// Fetcher self destroys itself after it is completed.
|
||||
// TODO: cancel all forward requests after termination
|
||||
type Fetcher struct {
|
||||
protoRequestFunc RequestFunc // request function fetcher calls to issue retrieve request for a chunk
|
||||
addr storage.Address // the address of the chunk to be fetched
|
||||
offerC chan *enode.ID // channel of sources (peer node id strings)
|
||||
requestC chan uint8 // channel for incoming requests (with the hopCount value in it)
|
||||
searchTimeout time.Duration
|
||||
skipCheck bool
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Addr storage.Address // chunk address
|
||||
Source *enode.ID // nodeID of peer to request from (can be nil)
|
||||
SkipCheck bool // whether to offer the chunk first or deliver directly
|
||||
peersToSkip *sync.Map // peers not to request chunk from (only makes sense if source is nil)
|
||||
HopCount uint8 // number of forwarded requests (hops)
|
||||
}
|
||||
|
||||
// NewRequest returns a new instance of Request based on chunk address skip check and
|
||||
// a map of peers to skip.
|
||||
func NewRequest(addr storage.Address, skipCheck bool, peersToSkip *sync.Map) *Request {
|
||||
return &Request{
|
||||
Addr: addr,
|
||||
SkipCheck: skipCheck,
|
||||
peersToSkip: peersToSkip,
|
||||
}
|
||||
}
|
||||
|
||||
// SkipPeer returns if the peer with nodeID should not be requested to deliver a chunk.
|
||||
// Peers to skip are kept per Request and for a time period of RequestTimeout.
|
||||
// This function is used in stream package in Delivery.RequestFromPeers to optimize
|
||||
// requests for chunks.
|
||||
func (r *Request) SkipPeer(nodeID string) bool {
|
||||
val, ok := r.peersToSkip.Load(nodeID)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
t, ok := val.(time.Time)
|
||||
if ok && time.Now().After(t.Add(RequestTimeout)) {
|
||||
// deadline expired
|
||||
r.peersToSkip.Delete(nodeID)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// FetcherFactory is initialised with a request function and can create fetchers
|
||||
type FetcherFactory struct {
|
||||
request RequestFunc
|
||||
skipCheck bool
|
||||
}
|
||||
|
||||
// NewFetcherFactory takes a request function and skip check parameter and creates a FetcherFactory
|
||||
func NewFetcherFactory(request RequestFunc, skipCheck bool) *FetcherFactory {
|
||||
return &FetcherFactory{
|
||||
request: request,
|
||||
skipCheck: skipCheck,
|
||||
}
|
||||
}
|
||||
|
||||
// New constructs a new Fetcher, for the given chunk. All peers in peersToSkip
|
||||
// are not requested to deliver the given chunk. peersToSkip should always
|
||||
// contain the peers which are actively requesting this chunk, to make sure we
|
||||
// don't request back the chunks from them.
|
||||
// The created Fetcher is started and returned.
|
||||
func (f *FetcherFactory) New(ctx context.Context, source storage.Address, peers *sync.Map) storage.NetFetcher {
|
||||
fetcher := NewFetcher(ctx, source, f.request, f.skipCheck)
|
||||
go fetcher.run(peers)
|
||||
return fetcher
|
||||
}
|
||||
|
||||
// NewFetcher creates a new Fetcher for the given chunk address using the given request function.
|
||||
func NewFetcher(ctx context.Context, addr storage.Address, rf RequestFunc, skipCheck bool) *Fetcher {
|
||||
return &Fetcher{
|
||||
addr: addr,
|
||||
protoRequestFunc: rf,
|
||||
offerC: make(chan *enode.ID),
|
||||
requestC: make(chan uint8),
|
||||
searchTimeout: defaultSearchTimeout,
|
||||
skipCheck: skipCheck,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// Offer is called when an upstream peer offers the chunk via syncing as part of `OfferedHashesMsg` and the node does not have the chunk locally.
|
||||
func (f *Fetcher) Offer(source *enode.ID) {
|
||||
// First we need to have this select to make sure that we return if context is done
|
||||
select {
|
||||
case <-f.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// This select alone would not guarantee that we return of context is done, it could potentially
|
||||
// push to offerC instead if offerC is available (see number 2 in https://golang.org/ref/spec#Select_statements)
|
||||
select {
|
||||
case f.offerC <- source:
|
||||
case <-f.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// Request is called when an upstream peer request the chunk as part of `RetrieveRequestMsg`, or from a local request through FileStore, and the node does not have the chunk locally.
|
||||
func (f *Fetcher) Request(hopCount uint8) {
|
||||
// First we need to have this select to make sure that we return if context is done
|
||||
select {
|
||||
case <-f.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if hopCount >= maxHopCount {
|
||||
log.Debug("fetcher request hop count limit reached", "hops", hopCount)
|
||||
return
|
||||
}
|
||||
|
||||
// This select alone would not guarantee that we return of context is done, it could potentially
|
||||
// push to offerC instead if offerC is available (see number 2 in https://golang.org/ref/spec#Select_statements)
|
||||
select {
|
||||
case f.requestC <- hopCount + 1:
|
||||
case <-f.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// start prepares the Fetcher
|
||||
// it keeps the Fetcher alive within the lifecycle of the passed context
|
||||
func (f *Fetcher) run(peers *sync.Map) {
|
||||
var (
|
||||
doRequest bool // determines if retrieval is initiated in the current iteration
|
||||
wait *time.Timer // timer for search timeout
|
||||
waitC <-chan time.Time // timer channel
|
||||
sources []*enode.ID // known sources, ie. peers that offered the chunk
|
||||
requested bool // true if the chunk was actually requested
|
||||
hopCount uint8
|
||||
)
|
||||
gone := make(chan *enode.ID) // channel to signal that a peer we requested from disconnected
|
||||
|
||||
// loop that keeps the fetching process alive
|
||||
// after every request a timer is set. If this goes off we request again from another peer
|
||||
// note that the previous request is still alive and has the chance to deliver, so
|
||||
// requesting again extends the search. ie.,
|
||||
// if a peer we requested from is gone we issue a new request, so the number of active
|
||||
// requests never decreases
|
||||
for {
|
||||
select {
|
||||
|
||||
// incoming offer
|
||||
case source := <-f.offerC:
|
||||
log.Trace("new source", "peer addr", source, "request addr", f.addr)
|
||||
// 1) the chunk is offered by a syncing peer
|
||||
// add to known sources
|
||||
sources = append(sources, source)
|
||||
// launch a request to the source iff the chunk was requested (not just expected because its offered by a syncing peer)
|
||||
doRequest = requested
|
||||
|
||||
// incoming request
|
||||
case hopCount = <-f.requestC:
|
||||
// 2) chunk is requested, set requested flag
|
||||
// launch a request iff none been launched yet
|
||||
doRequest = !requested
|
||||
log.Trace("new request", "request addr", f.addr, "doRequest", doRequest)
|
||||
requested = true
|
||||
|
||||
// peer we requested from is gone. fall back to another
|
||||
// and remove the peer from the peers map
|
||||
case id := <-gone:
|
||||
peers.Delete(id.String())
|
||||
doRequest = requested
|
||||
log.Trace("peer gone", "peer id", id.String(), "request addr", f.addr, "doRequest", doRequest)
|
||||
|
||||
// search timeout: too much time passed since the last request,
|
||||
// extend the search to a new peer if we can find one
|
||||
case <-waitC:
|
||||
doRequest = requested
|
||||
log.Trace("search timed out: requesting", "request addr", f.addr, "doRequest", doRequest)
|
||||
|
||||
// all Fetcher context closed, can quit
|
||||
case <-f.ctx.Done():
|
||||
log.Trace("terminate fetcher", "request addr", f.addr)
|
||||
// TODO: send cancellations to all peers left over in peers map (i.e., those we requested from)
|
||||
return
|
||||
}
|
||||
|
||||
// need to issue a new request
|
||||
if doRequest {
|
||||
var err error
|
||||
sources, err = f.doRequest(gone, peers, sources, hopCount)
|
||||
if err != nil {
|
||||
log.Info("unable to request", "request addr", f.addr, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// if wait channel is not set, set it to a timer
|
||||
if requested {
|
||||
if wait == nil {
|
||||
wait = time.NewTimer(f.searchTimeout)
|
||||
defer wait.Stop()
|
||||
waitC = wait.C
|
||||
} else {
|
||||
// stop the timer and drain the channel if it was not drained earlier
|
||||
if !wait.Stop() {
|
||||
select {
|
||||
case <-wait.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
// reset the timer to go off after defaultSearchTimeout
|
||||
wait.Reset(f.searchTimeout)
|
||||
}
|
||||
}
|
||||
doRequest = false
|
||||
}
|
||||
}
|
||||
|
||||
// doRequest attempts at finding a peer to request the chunk from
|
||||
// * first it tries to request explicitly from peers that are known to have offered the chunk
|
||||
// * if there are no such peers (available) it tries to request it from a peer closest to the chunk address
|
||||
// excluding those in the peersToSkip map
|
||||
// * if no such peer is found an error is returned
|
||||
//
|
||||
// if a request is successful,
|
||||
// * the peer's address is added to the set of peers to skip
|
||||
// * the peer's address is removed from prospective sources, and
|
||||
// * a go routine is started that reports on the gone channel if the peer is disconnected (or terminated their streamer)
|
||||
func (f *Fetcher) doRequest(gone chan *enode.ID, peersToSkip *sync.Map, sources []*enode.ID, hopCount uint8) ([]*enode.ID, error) {
|
||||
var i int
|
||||
var sourceID *enode.ID
|
||||
var quit chan struct{}
|
||||
|
||||
req := &Request{
|
||||
Addr: f.addr,
|
||||
SkipCheck: f.skipCheck,
|
||||
peersToSkip: peersToSkip,
|
||||
HopCount: hopCount,
|
||||
}
|
||||
|
||||
foundSource := false
|
||||
// iterate over known sources
|
||||
for i = 0; i < len(sources); i++ {
|
||||
req.Source = sources[i]
|
||||
var err error
|
||||
log.Trace("fetcher.doRequest", "request addr", f.addr, "peer", req.Source.String())
|
||||
sourceID, quit, err = f.protoRequestFunc(f.ctx, req)
|
||||
if err == nil {
|
||||
// remove the peer from known sources
|
||||
// Note: we can modify the source although we are looping on it, because we break from the loop immediately
|
||||
sources = append(sources[:i], sources[i+1:]...)
|
||||
foundSource = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no known sources, or none available, we try request from a closest node
|
||||
if !foundSource {
|
||||
req.Source = nil
|
||||
var err error
|
||||
sourceID, quit, err = f.protoRequestFunc(f.ctx, req)
|
||||
if err != nil {
|
||||
// if no peers found to request from
|
||||
return sources, err
|
||||
}
|
||||
}
|
||||
// add peer to the set of peers to skip from now
|
||||
peersToSkip.Store(sourceID.String(), time.Now())
|
||||
|
||||
// if the quit channel is closed, it indicates that the source peer we requested from
|
||||
// disconnected or terminated its streamer
|
||||
// here start a go routine that watches this channel and reports the source peer on the gone channel
|
||||
// this go routine quits if the fetcher global context is done to prevent process leak
|
||||
go func() {
|
||||
select {
|
||||
case <-quit:
|
||||
gone <- sourceID
|
||||
case <-f.ctx.Done():
|
||||
}
|
||||
|
||||
// finish the request span
|
||||
spanId := fmt.Sprintf("stream.send.request.%v.%v", *sourceID, req.Addr)
|
||||
span := tracing.ShiftSpanByKey(spanId)
|
||||
|
||||
if span != nil {
|
||||
span.LogFields(olog.String("finish", "from doRequest"))
|
||||
span.Finish()
|
||||
}
|
||||
}()
|
||||
return sources, nil
|
||||
}
|
@ -1,476 +0,0 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
var requestedPeerID = enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8")
|
||||
var sourcePeerID = enode.HexID("99d8594b52298567d2ca3f4c441a5ba0140ee9245e26460d01102a52773c73b9")
|
||||
|
||||
// mockRequester pushes every request to the requestC channel when its doRequest function is called
|
||||
type mockRequester struct {
|
||||
// requests []Request
|
||||
requestC chan *Request // when a request is coming it is pushed to requestC
|
||||
waitTimes []time.Duration // with waitTimes[i] you can define how much to wait on the ith request (optional)
|
||||
count int //counts the number of requests
|
||||
quitC chan struct{}
|
||||
}
|
||||
|
||||
func newMockRequester(waitTimes ...time.Duration) *mockRequester {
|
||||
return &mockRequester{
|
||||
requestC: make(chan *Request),
|
||||
waitTimes: waitTimes,
|
||||
quitC: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockRequester) doRequest(ctx context.Context, request *Request) (*enode.ID, chan struct{}, error) {
|
||||
waitTime := time.Duration(0)
|
||||
if m.count < len(m.waitTimes) {
|
||||
waitTime = m.waitTimes[m.count]
|
||||
m.count++
|
||||
}
|
||||
time.Sleep(waitTime)
|
||||
m.requestC <- request
|
||||
|
||||
// if there is a Source in the request use that, if not use the global requestedPeerId
|
||||
source := request.Source
|
||||
if source == nil {
|
||||
source = &requestedPeerID
|
||||
}
|
||||
return source, m.quitC, nil
|
||||
}
|
||||
|
||||
// TestFetcherSingleRequest creates a Fetcher using mockRequester, and run it with a sample set of peers to skip.
|
||||
// mockRequester pushes a Request on a channel every time the request function is called. Using
|
||||
// this channel we test if calling Fetcher.Request calls the request function, and whether it uses
|
||||
// the correct peers to skip which we provided for the fetcher.run function.
|
||||
func TestFetcherSingleRequest(t *testing.T) {
|
||||
requester := newMockRequester()
|
||||
addr := make([]byte, 32)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
|
||||
|
||||
peers := []string{"a", "b", "c", "d"}
|
||||
peersToSkip := &sync.Map{}
|
||||
for _, p := range peers {
|
||||
peersToSkip.Store(p, time.Now())
|
||||
}
|
||||
|
||||
go fetcher.run(peersToSkip)
|
||||
|
||||
fetcher.Request(0)
|
||||
|
||||
select {
|
||||
case request := <-requester.requestC:
|
||||
// request should contain all peers from peersToSkip provided to the fetcher
|
||||
for _, p := range peers {
|
||||
if _, ok := request.peersToSkip.Load(p); !ok {
|
||||
t.Fatalf("request.peersToSkip misses peer")
|
||||
}
|
||||
}
|
||||
|
||||
// source peer should be also added to peersToSkip eventually
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if _, ok := request.peersToSkip.Load(requestedPeerID.String()); !ok {
|
||||
t.Fatalf("request.peersToSkip does not contain peer returned by the request function")
|
||||
}
|
||||
|
||||
// hopCount in the forwarded request should be incremented
|
||||
if request.HopCount != 1 {
|
||||
t.Fatalf("Expected request.HopCount 1 got %v", request.HopCount)
|
||||
}
|
||||
|
||||
// fetch should trigger a request, if it doesn't happen in time, test should fail
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
t.Fatalf("fetch timeout")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCancelStopsFetcher tests that a cancelled fetcher does not initiate further requests even if its fetch function is called
|
||||
func TestFetcherCancelStopsFetcher(t *testing.T) {
|
||||
requester := newMockRequester()
|
||||
addr := make([]byte, 32)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
|
||||
|
||||
peersToSkip := &sync.Map{}
|
||||
|
||||
// we start the fetcher, and then we immediately cancel the context
|
||||
go fetcher.run(peersToSkip)
|
||||
cancel()
|
||||
|
||||
// we call Request with an active context
|
||||
fetcher.Request(0)
|
||||
|
||||
// fetcher should not initiate request, we can only check by waiting a bit and making sure no request is happening
|
||||
select {
|
||||
case <-requester.requestC:
|
||||
t.Fatalf("cancelled fetcher initiated request")
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetchCancelStopsRequest tests that calling a Request function with a cancelled context does not initiate a request
|
||||
func TestFetcherCancelStopsRequest(t *testing.T) {
|
||||
t.Skip("since context is now per fetcher, this test is likely redundant")
|
||||
|
||||
requester := newMockRequester(100 * time.Millisecond)
|
||||
addr := make([]byte, 32)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
|
||||
|
||||
peersToSkip := &sync.Map{}
|
||||
|
||||
// we start the fetcher with an active context
|
||||
go fetcher.run(peersToSkip)
|
||||
|
||||
// we call Request with a cancelled context
|
||||
fetcher.Request(0)
|
||||
|
||||
// fetcher should not initiate request, we can only check by waiting a bit and making sure no request is happening
|
||||
select {
|
||||
case <-requester.requestC:
|
||||
t.Fatalf("cancelled fetch function initiated request")
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
}
|
||||
|
||||
// if there is another Request with active context, there should be a request, because the fetcher itself is not cancelled
|
||||
fetcher.Request(0)
|
||||
|
||||
select {
|
||||
case <-requester.requestC:
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
t.Fatalf("expected request")
|
||||
}
|
||||
}
|
||||
|
||||
// TestOfferUsesSource tests Fetcher Offer behavior.
|
||||
// In this case there should be 1 (and only one) request initiated from the source peer, and the
|
||||
// source nodeid should appear in the peersToSkip map.
|
||||
func TestFetcherOfferUsesSource(t *testing.T) {
|
||||
requester := newMockRequester(100 * time.Millisecond)
|
||||
addr := make([]byte, 32)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
|
||||
|
||||
peersToSkip := &sync.Map{}
|
||||
|
||||
// start the fetcher
|
||||
go fetcher.run(peersToSkip)
|
||||
|
||||
// call the Offer function with the source peer
|
||||
fetcher.Offer(&sourcePeerID)
|
||||
|
||||
// fetcher should not initiate request
|
||||
select {
|
||||
case <-requester.requestC:
|
||||
t.Fatalf("fetcher initiated request")
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
}
|
||||
|
||||
// call Request after the Offer
|
||||
fetcher.Request(0)
|
||||
|
||||
// there should be exactly 1 request coming from fetcher
|
||||
var request *Request
|
||||
select {
|
||||
case request = <-requester.requestC:
|
||||
if *request.Source != sourcePeerID {
|
||||
t.Fatalf("Expected source id %v got %v", sourcePeerID, request.Source)
|
||||
}
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
t.Fatalf("fetcher did not initiate request")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-requester.requestC:
|
||||
t.Fatalf("Fetcher number of requests expected 1 got 2")
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
}
|
||||
|
||||
// source peer should be added to peersToSkip eventually
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if _, ok := request.peersToSkip.Load(sourcePeerID.String()); !ok {
|
||||
t.Fatalf("SourcePeerId not added to peersToSkip")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetcherOfferAfterRequestUsesSourceFromContext(t *testing.T) {
|
||||
requester := newMockRequester(100 * time.Millisecond)
|
||||
addr := make([]byte, 32)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
|
||||
|
||||
peersToSkip := &sync.Map{}
|
||||
|
||||
// start the fetcher
|
||||
go fetcher.run(peersToSkip)
|
||||
|
||||
// call Request first
|
||||
fetcher.Request(0)
|
||||
|
||||
// there should be a request coming from fetcher
|
||||
var request *Request
|
||||
select {
|
||||
case request = <-requester.requestC:
|
||||
if request.Source != nil {
|
||||
t.Fatalf("Incorrect source peer id, expected nil got %v", request.Source)
|
||||
}
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
t.Fatalf("fetcher did not initiate request")
|
||||
}
|
||||
|
||||
// after the Request call Offer
|
||||
fetcher.Offer(&sourcePeerID)
|
||||
|
||||
// there should be a request coming from fetcher
|
||||
select {
|
||||
case request = <-requester.requestC:
|
||||
if *request.Source != sourcePeerID {
|
||||
t.Fatalf("Incorrect source peer id, expected %v got %v", sourcePeerID, request.Source)
|
||||
}
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
t.Fatalf("fetcher did not initiate request")
|
||||
}
|
||||
|
||||
// source peer should be added to peersToSkip eventually
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if _, ok := request.peersToSkip.Load(sourcePeerID.String()); !ok {
|
||||
t.Fatalf("SourcePeerId not added to peersToSkip")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetcherRetryOnTimeout tests that fetch retries after searchTimeOut has passed
|
||||
func TestFetcherRetryOnTimeout(t *testing.T) {
|
||||
requester := newMockRequester()
|
||||
addr := make([]byte, 32)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
|
||||
// set searchTimeOut to low value so the test is quicker
|
||||
fetcher.searchTimeout = 250 * time.Millisecond
|
||||
|
||||
peersToSkip := &sync.Map{}
|
||||
|
||||
// start the fetcher
|
||||
go fetcher.run(peersToSkip)
|
||||
|
||||
// call the fetch function with an active context
|
||||
fetcher.Request(0)
|
||||
|
||||
// after 100ms the first request should be initiated
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
select {
|
||||
case <-requester.requestC:
|
||||
default:
|
||||
t.Fatalf("fetch did not initiate request")
|
||||
}
|
||||
|
||||
// after another 100ms no new request should be initiated, because search timeout is 250ms
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
select {
|
||||
case <-requester.requestC:
|
||||
t.Fatalf("unexpected request from fetcher")
|
||||
default:
|
||||
}
|
||||
|
||||
// after another 300ms search timeout is over, there should be a new request
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
select {
|
||||
case <-requester.requestC:
|
||||
default:
|
||||
t.Fatalf("fetch did not retry request")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetcherFactory creates a FetcherFactory and checks if the factory really creates and starts
|
||||
// a Fetcher when it return a fetch function. We test the fetching functionality just by checking if
|
||||
// a request is initiated when the fetch function is called
|
||||
func TestFetcherFactory(t *testing.T) {
|
||||
requester := newMockRequester(100 * time.Millisecond)
|
||||
addr := make([]byte, 32)
|
||||
fetcherFactory := NewFetcherFactory(requester.doRequest, false)
|
||||
|
||||
peersToSkip := &sync.Map{}
|
||||
|
||||
fetcher := fetcherFactory.New(context.Background(), addr, peersToSkip)
|
||||
|
||||
fetcher.Request(0)
|
||||
|
||||
// check if the created fetchFunction really starts a fetcher and initiates a request
|
||||
select {
|
||||
case <-requester.requestC:
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
t.Fatalf("fetch timeout")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFetcherRequestQuitRetriesRequest(t *testing.T) {
|
||||
requester := newMockRequester()
|
||||
addr := make([]byte, 32)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
|
||||
|
||||
// make sure the searchTimeout is long so it is sure the request is not
|
||||
// retried because of timeout
|
||||
fetcher.searchTimeout = 10 * time.Second
|
||||
|
||||
peersToSkip := &sync.Map{}
|
||||
|
||||
go fetcher.run(peersToSkip)
|
||||
|
||||
fetcher.Request(0)
|
||||
|
||||
select {
|
||||
case <-requester.requestC:
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
t.Fatalf("request is not initiated")
|
||||
}
|
||||
|
||||
close(requester.quitC)
|
||||
|
||||
select {
|
||||
case <-requester.requestC:
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
t.Fatalf("request is not initiated after failed request")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRequestSkipPeer checks if PeerSkip function will skip provided peer
|
||||
// and not skip unknown one.
|
||||
func TestRequestSkipPeer(t *testing.T) {
|
||||
addr := make([]byte, 32)
|
||||
peers := []enode.ID{
|
||||
enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8"),
|
||||
enode.HexID("99d8594b52298567d2ca3f4c441a5ba0140ee9245e26460d01102a52773c73b9"),
|
||||
}
|
||||
|
||||
peersToSkip := new(sync.Map)
|
||||
peersToSkip.Store(peers[0].String(), time.Now())
|
||||
r := NewRequest(addr, false, peersToSkip)
|
||||
|
||||
if !r.SkipPeer(peers[0].String()) {
|
||||
t.Errorf("peer not skipped")
|
||||
}
|
||||
|
||||
if r.SkipPeer(peers[1].String()) {
|
||||
t.Errorf("peer skipped")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRequestSkipPeerExpired checks if a peer to skip is not skipped
|
||||
// after RequestTimeout has passed.
|
||||
func TestRequestSkipPeerExpired(t *testing.T) {
|
||||
addr := make([]byte, 32)
|
||||
peer := enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8")
|
||||
|
||||
// set RequestTimeout to a low value and reset it after the test
|
||||
defer func(t time.Duration) { RequestTimeout = t }(RequestTimeout)
|
||||
RequestTimeout = 250 * time.Millisecond
|
||||
|
||||
peersToSkip := new(sync.Map)
|
||||
peersToSkip.Store(peer.String(), time.Now())
|
||||
r := NewRequest(addr, false, peersToSkip)
|
||||
|
||||
if !r.SkipPeer(peer.String()) {
|
||||
t.Errorf("peer not skipped")
|
||||
}
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
if r.SkipPeer(peer.String()) {
|
||||
t.Errorf("peer skipped")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRequestSkipPeerPermanent checks if a peer to skip is not skipped
|
||||
// after RequestTimeout is not skipped if it is set for a permanent skipping
|
||||
// by value to peersToSkip map is not time.Duration.
|
||||
func TestRequestSkipPeerPermanent(t *testing.T) {
|
||||
addr := make([]byte, 32)
|
||||
peer := enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8")
|
||||
|
||||
// set RequestTimeout to a low value and reset it after the test
|
||||
defer func(t time.Duration) { RequestTimeout = t }(RequestTimeout)
|
||||
RequestTimeout = 250 * time.Millisecond
|
||||
|
||||
peersToSkip := new(sync.Map)
|
||||
peersToSkip.Store(peer.String(), true)
|
||||
r := NewRequest(addr, false, peersToSkip)
|
||||
|
||||
if !r.SkipPeer(peer.String()) {
|
||||
t.Errorf("peer not skipped")
|
||||
}
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
if !r.SkipPeer(peer.String()) {
|
||||
t.Errorf("peer not skipped")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetcherMaxHopCount(t *testing.T) {
|
||||
requester := newMockRequester()
|
||||
addr := make([]byte, 32)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
fetcher := NewFetcher(ctx, addr, requester.doRequest, true)
|
||||
|
||||
peersToSkip := &sync.Map{}
|
||||
|
||||
go fetcher.run(peersToSkip)
|
||||
|
||||
// if hopCount is already at max no request should be initiated
|
||||
select {
|
||||
case <-requester.requestC:
|
||||
t.Fatalf("cancelled fetcher initiated request")
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ var DefaultTestNetworkID = rand.Uint64()
|
||||
// BzzSpec is the spec of the generic swarm handshake
|
||||
var BzzSpec = &protocols.Spec{
|
||||
Name: "bzz",
|
||||
Version: 10,
|
||||
Version: 11,
|
||||
MaxMsgSize: 10 * 1024 * 1024,
|
||||
Messages: []interface{}{
|
||||
HandshakeMsg{},
|
||||
@ -53,7 +53,7 @@ var BzzSpec = &protocols.Spec{
|
||||
// DiscoverySpec is the spec for the bzz discovery subprotocols
|
||||
var DiscoverySpec = &protocols.Spec{
|
||||
Name: "hive",
|
||||
Version: 9,
|
||||
Version: 10,
|
||||
MaxMsgSize: 10 * 1024 * 1024,
|
||||
Messages: []interface{}{
|
||||
peersMsg{},
|
||||
|
@ -36,7 +36,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TestProtocolVersion = 10
|
||||
TestProtocolVersion = 11
|
||||
)
|
||||
|
||||
var TestProtocolNetworkID = DefaultTestNetworkID
|
||||
|
@ -32,7 +32,7 @@ func TestServiceBucket(t *testing.T) {
|
||||
testKey := "Key"
|
||||
testValue := "Value"
|
||||
|
||||
sim := New(map[string]ServiceFunc{
|
||||
sim := NewInProc(map[string]ServiceFunc{
|
||||
"noop": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) {
|
||||
b.Store(testKey, testValue+ctx.Config.ID.String())
|
||||
return newNoopService(), nil, nil
|
||||
|
@ -28,7 +28,7 @@ import (
|
||||
// and waits for the number of connection events to
|
||||
// be received.
|
||||
func TestPeerEvents(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
_, err := sim.AddNodes(2)
|
||||
@ -68,7 +68,7 @@ func TestPeerEvents(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPeerEventsTimeout(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
_, err := sim.AddNodes(2)
|
||||
|
@ -34,7 +34,7 @@ import (
|
||||
// all nodes have the their Kademlias healthy.
|
||||
func ExampleSimulation_WaitTillHealthy() {
|
||||
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
sim := simulation.NewInProc(map[string]simulation.ServiceFunc{
|
||||
"bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) {
|
||||
addr := network.NewAddr(ctx.Config.Node())
|
||||
hp := network.NewHiveParams()
|
||||
@ -77,7 +77,7 @@ func ExampleSimulation_WaitTillHealthy() {
|
||||
|
||||
// Watch all peer events in the simulation network, buy receiving from a channel.
|
||||
func ExampleSimulation_PeerEvents() {
|
||||
sim := simulation.New(nil)
|
||||
sim := simulation.NewInProc(nil)
|
||||
defer sim.Close()
|
||||
|
||||
events := sim.PeerEvents(context.Background(), sim.NodeIDs())
|
||||
@ -95,7 +95,7 @@ func ExampleSimulation_PeerEvents() {
|
||||
|
||||
// Detect when a nodes drop a peer.
|
||||
func ExampleSimulation_PeerEvents_disconnections() {
|
||||
sim := simulation.New(nil)
|
||||
sim := simulation.NewInProc(nil)
|
||||
defer sim.Close()
|
||||
|
||||
disconnections := sim.PeerEvents(
|
||||
@ -118,7 +118,7 @@ func ExampleSimulation_PeerEvents_disconnections() {
|
||||
// Watch multiple types of events or messages. In this case, they differ only
|
||||
// by MsgCode, but filters can be set for different types or protocols, too.
|
||||
func ExampleSimulation_PeerEvents_multipleFilters() {
|
||||
sim := simulation.New(nil)
|
||||
sim := simulation.NewInProc(nil)
|
||||
defer sim.Close()
|
||||
|
||||
msgs := sim.PeerEvents(
|
||||
|
@ -35,7 +35,7 @@ func TestSimulationWithHTTPServer(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
sim := New(
|
||||
sim := NewInProc(
|
||||
map[string]ServiceFunc{
|
||||
"noop": func(_ *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) {
|
||||
return newNoopService(), nil, nil
|
||||
|
@ -46,7 +46,7 @@ func TestWaitTillHealthy(t *testing.T) {
|
||||
testNodesNum := 10
|
||||
|
||||
// create the first simulation
|
||||
sim := New(createSimServiceMap(true))
|
||||
sim := NewInProc(createSimServiceMap(true))
|
||||
|
||||
// connect and...
|
||||
nodeIDs, err := sim.AddNodesAndConnectRing(testNodesNum)
|
||||
@ -88,7 +88,7 @@ func TestWaitTillHealthy(t *testing.T) {
|
||||
// close the initial simulation
|
||||
sim.Close()
|
||||
// create a control simulation
|
||||
controlSim := New(createSimServiceMap(false))
|
||||
controlSim := NewInProc(createSimServiceMap(false))
|
||||
defer controlSim.Close()
|
||||
|
||||
// load the snapshot into this control simulation
|
||||
@ -158,7 +158,7 @@ func createSimServiceMap(discovery bool) map[string]ServiceFunc {
|
||||
func TestWaitTillSnapshotRecreated(t *testing.T) {
|
||||
t.Skip("test is flaky. disabling until underlying problem is addressed")
|
||||
var err error
|
||||
sim := New(createSimServiceMap(true))
|
||||
sim := NewInProc(createSimServiceMap(true))
|
||||
_, err = sim.AddNodesAndConnectRing(16)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -177,7 +177,7 @@ func TestWaitTillSnapshotRecreated(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
controlSim := New(createSimServiceMap(false))
|
||||
controlSim := NewInProc(createSimServiceMap(false))
|
||||
defer controlSim.Close()
|
||||
err = controlSim.Net.Load(snap)
|
||||
if err != nil {
|
||||
|
@ -32,7 +32,7 @@ import (
|
||||
)
|
||||
|
||||
func TestUpDownNodeIDs(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
ids, err := sim.AddNodes(10)
|
||||
@ -99,7 +99,7 @@ func equalNodeIDs(one, other []enode.ID) bool {
|
||||
}
|
||||
|
||||
func TestAddNode(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
id, err := sim.AddNode()
|
||||
@ -118,7 +118,7 @@ func TestAddNode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddNodeWithMsgEvents(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
id, err := sim.AddNode(AddNodeWithMsgEvents(true))
|
||||
@ -141,7 +141,7 @@ func TestAddNodeWithMsgEvents(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddNodeWithService(t *testing.T) {
|
||||
sim := New(map[string]ServiceFunc{
|
||||
sim := NewInProc(map[string]ServiceFunc{
|
||||
"noop1": noopServiceFunc,
|
||||
"noop2": noopServiceFunc,
|
||||
})
|
||||
@ -162,7 +162,7 @@ func TestAddNodeWithService(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddNodeMultipleServices(t *testing.T) {
|
||||
sim := New(map[string]ServiceFunc{
|
||||
sim := NewInProc(map[string]ServiceFunc{
|
||||
"noop1": noopServiceFunc,
|
||||
"noop2": noopService2Func,
|
||||
})
|
||||
@ -183,7 +183,7 @@ func TestAddNodeMultipleServices(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddNodeDuplicateServiceError(t *testing.T) {
|
||||
sim := New(map[string]ServiceFunc{
|
||||
sim := NewInProc(map[string]ServiceFunc{
|
||||
"noop1": noopServiceFunc,
|
||||
"noop2": noopServiceFunc,
|
||||
})
|
||||
@ -197,7 +197,7 @@ func TestAddNodeDuplicateServiceError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddNodes(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
nodesCount := 12
|
||||
@ -219,7 +219,7 @@ func TestAddNodes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddNodesAndConnectFull(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
n := 12
|
||||
@ -233,7 +233,7 @@ func TestAddNodesAndConnectFull(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddNodesAndConnectChain(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
_, err := sim.AddNodesAndConnectChain(12)
|
||||
@ -252,7 +252,7 @@ func TestAddNodesAndConnectChain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddNodesAndConnectRing(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
ids, err := sim.AddNodesAndConnectRing(12)
|
||||
@ -264,7 +264,7 @@ func TestAddNodesAndConnectRing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddNodesAndConnectStar(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
ids, err := sim.AddNodesAndConnectStar(12)
|
||||
@ -278,7 +278,7 @@ func TestAddNodesAndConnectStar(t *testing.T) {
|
||||
//To test that uploading a snapshot works
|
||||
func TestUploadSnapshot(t *testing.T) {
|
||||
log.Debug("Creating simulation")
|
||||
s := New(map[string]ServiceFunc{
|
||||
s := NewInProc(map[string]ServiceFunc{
|
||||
"bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) {
|
||||
addr := network.NewAddr(ctx.Config.Node())
|
||||
hp := network.NewHiveParams()
|
||||
@ -317,7 +317,7 @@ func TestUploadSnapshot(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStartStopNode(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
id, err := sim.AddNode()
|
||||
@ -353,7 +353,7 @@ func TestStartStopNode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStartStopRandomNode(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
_, err := sim.AddNodes(3)
|
||||
@ -392,7 +392,7 @@ func TestStartStopRandomNode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStartStopRandomNodes(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
_, err := sim.AddNodes(10)
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
)
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
id, err := sim.AddNode()
|
||||
|
@ -19,7 +19,9 @@ package simulation
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -31,6 +33,11 @@ import (
|
||||
"github.com/ethersphere/swarm/network"
|
||||
)
|
||||
|
||||
const (
|
||||
SimulationTypeInproc = iota
|
||||
SimulationTypeExec
|
||||
)
|
||||
|
||||
// Common errors that are returned by functions in this package.
|
||||
var (
|
||||
ErrNodeNotFound = errors.New("node not found")
|
||||
@ -50,6 +57,8 @@ type Simulation struct {
|
||||
done chan struct{}
|
||||
mu sync.RWMutex
|
||||
neighbourhoodSize int
|
||||
baseDir string
|
||||
typ int
|
||||
|
||||
httpSrv *http.Server //attach a HTTP server via SimulationOptions
|
||||
handler *simulations.Server //HTTP handler for the server
|
||||
@ -71,19 +80,66 @@ type ServiceFunc func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Se
|
||||
// every ServiceFunc must return a node.Service of the unique type.
|
||||
// This restriction is required by node.Node.Start() function
|
||||
// which is used to start node.Service returned by ServiceFunc.
|
||||
func New(services map[string]ServiceFunc) (s *Simulation) {
|
||||
func NewInProc(services map[string]ServiceFunc) (s *Simulation) {
|
||||
s = &Simulation{
|
||||
buckets: make(map[enode.ID]*sync.Map),
|
||||
done: make(chan struct{}),
|
||||
neighbourhoodSize: network.NewKadParams().NeighbourhoodSize,
|
||||
typ: SimulationTypeInproc,
|
||||
}
|
||||
|
||||
s.addServices(services)
|
||||
adapterServices := s.toAdapterServices(services)
|
||||
|
||||
s.Net = simulations.NewNetwork(
|
||||
adapters.NewTCPAdapter(adapterServices),
|
||||
&simulations.NetworkConfig{ID: "0"},
|
||||
)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// NewExec does the same as New but lets the caller specify the adapter to use
|
||||
func NewExec(services map[string]ServiceFunc) (s *Simulation, err error) {
|
||||
s = &Simulation{
|
||||
buckets: make(map[enode.ID]*sync.Map),
|
||||
done: make(chan struct{}),
|
||||
neighbourhoodSize: network.NewKadParams().NeighbourhoodSize,
|
||||
typ: SimulationTypeExec,
|
||||
}
|
||||
|
||||
s.addServices(services)
|
||||
adapterServices := s.toAdapterServices(services)
|
||||
|
||||
// exec adapters register services up front, not at node creation time
|
||||
adapters.RegisterServices(adapterServices)
|
||||
|
||||
s.baseDir, err = ioutil.TempDir("", "swarm-sim")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Net = simulations.NewNetwork(
|
||||
adapters.NewExecAdapter(s.baseDir),
|
||||
&simulations.NetworkConfig{ID: "0"},
|
||||
)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// add names of available services to simulation
|
||||
func (s *Simulation) addServices(services map[string]ServiceFunc) {
|
||||
for name := range services {
|
||||
s.serviceNames = append(s.serviceNames, name)
|
||||
}
|
||||
}
|
||||
|
||||
// convert services array for use with adapters.RegisterServices
|
||||
func (s *Simulation) toAdapterServices(services map[string]ServiceFunc) map[string]adapters.ServiceFunc {
|
||||
adapterServices := make(map[string]adapters.ServiceFunc, len(services))
|
||||
for name, serviceFunc := range services {
|
||||
// Scope this variables correctly
|
||||
// as they will be in the adapterServices[name] function accessed later.
|
||||
name, serviceFunc := name, serviceFunc
|
||||
s.serviceNames = append(s.serviceNames, name)
|
||||
adapterServices[name] = func(ctx *adapters.ServiceContext) (node.Service, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@ -102,13 +158,7 @@ func New(services map[string]ServiceFunc) (s *Simulation) {
|
||||
return service, nil
|
||||
}
|
||||
}
|
||||
|
||||
s.Net = simulations.NewNetwork(
|
||||
adapters.NewTCPAdapter(adapterServices),
|
||||
&simulations.NetworkConfig{ID: "0"},
|
||||
)
|
||||
|
||||
return s
|
||||
return adapterServices
|
||||
}
|
||||
|
||||
// RunFunc is the function that will be called
|
||||
@ -208,6 +258,9 @@ func (s *Simulation) Close() {
|
||||
|
||||
s.shutdownWG.Wait()
|
||||
s.Net.Shutdown()
|
||||
if s.baseDir != "" {
|
||||
os.RemoveAll(s.baseDir)
|
||||
}
|
||||
}
|
||||
|
||||
// Done returns a channel that is closed when the simulation
|
||||
|
@ -43,7 +43,7 @@ func init() {
|
||||
|
||||
// TestRun tests if Run method calls RunFunc and if it handles context properly.
|
||||
func TestRun(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
defer sim.Close()
|
||||
|
||||
t.Run("call", func(t *testing.T) {
|
||||
@ -104,7 +104,7 @@ func TestClose(t *testing.T) {
|
||||
|
||||
sleep := 50 * time.Millisecond
|
||||
|
||||
sim := New(map[string]ServiceFunc{
|
||||
sim := NewInProc(map[string]ServiceFunc{
|
||||
"noop": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) {
|
||||
return newNoopService(), func() {
|
||||
time.Sleep(sleep)
|
||||
@ -151,7 +151,7 @@ func TestClose(t *testing.T) {
|
||||
|
||||
// TestDone checks if Close method triggers the closing of done channel.
|
||||
func TestDone(t *testing.T) {
|
||||
sim := New(noopServiceFuncMap)
|
||||
sim := NewInProc(noopServiceFuncMap)
|
||||
sleep := 50 * time.Millisecond
|
||||
timeout := 2 * time.Second
|
||||
|
||||
|
@ -56,7 +56,6 @@ var (
|
||||
|
||||
bucketKeyStore = simulation.BucketKey("store")
|
||||
bucketKeyFileStore = simulation.BucketKey("filestore")
|
||||
bucketKeyNetStore = simulation.BucketKey("netstore")
|
||||
bucketKeyDelivery = simulation.BucketKey("delivery")
|
||||
bucketKeyRegistry = simulation.BucketKey("registry")
|
||||
|
||||
@ -81,7 +80,7 @@ func newNetStoreAndDelivery(ctx *adapters.ServiceContext, bucket *sync.Map) (*ne
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
netStore.NewNetFetcherFunc = network.NewFetcherFactory(delivery.RequestFromPeers, true).New
|
||||
netStore.RemoteGet = delivery.RequestFromPeers
|
||||
|
||||
return addr, netStore, delivery, cleanup, nil
|
||||
}
|
||||
@ -93,13 +92,13 @@ func newNetStoreAndDeliveryWithBzzAddr(ctx *adapters.ServiceContext, bucket *syn
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
netStore.NewNetFetcherFunc = network.NewFetcherFactory(delivery.RequestFromPeers, true).New
|
||||
netStore.RemoteGet = delivery.RequestFromPeers
|
||||
|
||||
return netStore, delivery, cleanup, nil
|
||||
}
|
||||
|
||||
// newNetStoreAndDeliveryWithRequestFunc is a constructor for NetStore and Delivery, used in Simulations, accepting any NetStore.RequestFunc
|
||||
func newNetStoreAndDeliveryWithRequestFunc(ctx *adapters.ServiceContext, bucket *sync.Map, rf network.RequestFunc) (*network.BzzAddr, *storage.NetStore, *Delivery, func(), error) {
|
||||
func newNetStoreAndDeliveryWithRequestFunc(ctx *adapters.ServiceContext, bucket *sync.Map, rf storage.RemoteGetFunc) (*network.BzzAddr, *storage.NetStore, *Delivery, func(), error) {
|
||||
addr := network.NewAddr(ctx.Config.Node())
|
||||
|
||||
netStore, delivery, cleanup, err := netStoreAndDeliveryWithAddr(ctx, bucket, addr)
|
||||
@ -107,7 +106,7 @@ func newNetStoreAndDeliveryWithRequestFunc(ctx *adapters.ServiceContext, bucket
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
netStore.NewNetFetcherFunc = network.NewFetcherFactory(rf, true).New
|
||||
netStore.RemoteGet = rf
|
||||
|
||||
return addr, netStore, delivery, cleanup, nil
|
||||
}
|
||||
@ -120,14 +119,9 @@ func netStoreAndDeliveryWithAddr(ctx *adapters.ServiceContext, bucket *sync.Map,
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
netStore, err := storage.NewNetStore(localStore, nil)
|
||||
if err != nil {
|
||||
localStore.Close()
|
||||
localStoreCleanup()
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams(), chunk.NewTags())
|
||||
netStore := storage.NewNetStore(localStore, enode.ID{})
|
||||
lnetStore := storage.NewLNetStore(netStore)
|
||||
fileStore := storage.NewFileStore(lnetStore, storage.NewFileStoreParams(), chunk.NewTags())
|
||||
|
||||
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||
delivery := NewDelivery(kad, netStore)
|
||||
@ -167,15 +161,11 @@ func newStreamerTester(registryOptions *RegistryOptions) (*p2ptest.ProtocolTeste
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
netStore, err := storage.NewNetStore(localStore, nil)
|
||||
if err != nil {
|
||||
localStore.Close()
|
||||
removeDataDir()
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
netStore := storage.NewNetStore(localStore, enode.ID{})
|
||||
|
||||
delivery := NewDelivery(to, netStore)
|
||||
netStore.NewNetFetcherFunc = network.NewFetcherFactory(delivery.RequestFromPeers, true).New
|
||||
netStore.RemoteGet = delivery.RequestFromPeers
|
||||
|
||||
intervalsStore := state.NewInmemoryStore()
|
||||
streamer := NewRegistry(addr.ID(), delivery, netStore, intervalsStore, registryOptions, nil)
|
||||
|
||||
|
@ -27,9 +27,9 @@ import (
|
||||
"github.com/ethersphere/swarm/chunk"
|
||||
"github.com/ethersphere/swarm/log"
|
||||
"github.com/ethersphere/swarm/network"
|
||||
"github.com/ethersphere/swarm/network/timeouts"
|
||||
"github.com/ethersphere/swarm/spancontext"
|
||||
"github.com/ethersphere/swarm/storage"
|
||||
"github.com/ethersphere/swarm/tracing"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
olog "github.com/opentracing/opentracing-go/log"
|
||||
)
|
||||
@ -39,9 +39,6 @@ var (
|
||||
handleRetrieveRequestMsgCount = metrics.NewRegisteredCounter("network.stream.handle_retrieve_request_msg.count", nil)
|
||||
retrieveChunkFail = metrics.NewRegisteredCounter("network.stream.retrieve_chunks_fail.count", nil)
|
||||
|
||||
requestFromPeersCount = metrics.NewRegisteredCounter("network.stream.request_from_peers.count", nil)
|
||||
requestFromPeersEachCount = metrics.NewRegisteredCounter("network.stream.request_from_peers_each.count", nil)
|
||||
|
||||
lastReceivedChunksMsg = metrics.GetOrRegisterGauge("network.stream.received_chunks", nil)
|
||||
)
|
||||
|
||||
@ -62,52 +59,42 @@ func NewDelivery(kad *network.Kademlia, netStore *storage.NetStore) *Delivery {
|
||||
|
||||
// RetrieveRequestMsg is the protocol msg for chunk retrieve requests
|
||||
type RetrieveRequestMsg struct {
|
||||
Addr storage.Address
|
||||
SkipCheck bool
|
||||
HopCount uint8
|
||||
Addr storage.Address
|
||||
}
|
||||
|
||||
func (d *Delivery) handleRetrieveRequestMsg(ctx context.Context, sp *Peer, req *RetrieveRequestMsg) error {
|
||||
log.Trace("received request", "peer", sp.ID(), "hash", req.Addr)
|
||||
log.Trace("handle retrieve request", "peer", sp.ID(), "hash", req.Addr)
|
||||
handleRetrieveRequestMsgCount.Inc(1)
|
||||
|
||||
var osp opentracing.Span
|
||||
ctx, osp = spancontext.StartSpan(
|
||||
ctx, osp := spancontext.StartSpan(
|
||||
ctx,
|
||||
"stream.handle.retrieve")
|
||||
"handle.retrieve.request")
|
||||
|
||||
osp.LogFields(olog.String("ref", req.Addr.String()))
|
||||
|
||||
var cancel func()
|
||||
// TODO: do something with this hardcoded timeout, maybe use TTL in the future
|
||||
ctx = context.WithValue(ctx, "peer", sp.ID().String())
|
||||
ctx = context.WithValue(ctx, "hopcount", req.HopCount)
|
||||
ctx, cancel = context.WithTimeout(ctx, network.RequestTimeout)
|
||||
defer osp.Finish()
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-d.quit:
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
ctx, cancel := context.WithTimeout(ctx, timeouts.FetcherGlobalTimeout)
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
defer osp.Finish()
|
||||
ch, err := d.netStore.Get(ctx, chunk.ModeGetRequest, req.Addr)
|
||||
if err != nil {
|
||||
retrieveChunkFail.Inc(1)
|
||||
log.Debug("ChunkStore.Get can not retrieve chunk", "peer", sp.ID().String(), "addr", req.Addr, "hopcount", req.HopCount, "err", err)
|
||||
return
|
||||
}
|
||||
syncing := false
|
||||
r := &storage.Request{
|
||||
Addr: req.Addr,
|
||||
Origin: sp.ID(),
|
||||
}
|
||||
chunk, err := d.netStore.Get(ctx, chunk.ModeGetRequest, r)
|
||||
if err != nil {
|
||||
retrieveChunkFail.Inc(1)
|
||||
log.Debug("ChunkStore.Get can not retrieve chunk", "peer", sp.ID().String(), "addr", req.Addr, "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = sp.Deliver(ctx, ch, Top, syncing)
|
||||
if err != nil {
|
||||
log.Warn("ERROR in handleRetrieveRequestMsg", "err", err)
|
||||
}
|
||||
osp.LogFields(olog.Bool("delivered", true))
|
||||
}()
|
||||
log.Trace("retrieve request, delivery", "ref", req.Addr, "peer", sp.ID())
|
||||
syncing := false
|
||||
err = sp.Deliver(ctx, chunk, 0, syncing)
|
||||
if err != nil {
|
||||
log.Error("sp.Deliver errored", "err", err)
|
||||
}
|
||||
osp.LogFields(olog.Bool("delivered", true))
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -189,57 +176,166 @@ func (d *Delivery) Close() {
|
||||
close(d.quit)
|
||||
}
|
||||
|
||||
// RequestFromPeers sends a chunk retrieve request to a peer
|
||||
// The most eligible peer that hasn't already been sent to is chosen
|
||||
// TODO: define "eligible"
|
||||
func (d *Delivery) RequestFromPeers(ctx context.Context, req *network.Request) (*enode.ID, chan struct{}, error) {
|
||||
requestFromPeersCount.Inc(1)
|
||||
var sp *Peer
|
||||
spID := req.Source
|
||||
// getOriginPo returns the originPo if the incoming Request has an Origin
|
||||
// if our node is the first node that requests this chunk, then we don't have an Origin,
|
||||
// and return -1
|
||||
// this is used only for tracing, and can probably be refactor so that we don't have to
|
||||
// iterater over Kademlia
|
||||
func (d *Delivery) getOriginPo(req *storage.Request) int {
|
||||
originPo := -1
|
||||
|
||||
if spID != nil {
|
||||
sp = d.getPeer(*spID)
|
||||
if sp == nil {
|
||||
return nil, nil, fmt.Errorf("source peer %v not found", spID.String())
|
||||
}
|
||||
} else {
|
||||
d.kad.EachConn(req.Addr[:], 255, func(p *network.Peer, po int) bool {
|
||||
id := p.ID()
|
||||
if p.LightNode {
|
||||
// skip light nodes
|
||||
return true
|
||||
}
|
||||
if req.SkipPeer(id.String()) {
|
||||
log.Trace("Delivery.RequestFromPeers: skip peer", "peer id", id)
|
||||
return true
|
||||
}
|
||||
sp = d.getPeer(id)
|
||||
// sp is nil, when we encounter a peer that is not registered for delivery, i.e. doesn't support the `stream` protocol
|
||||
if sp == nil {
|
||||
return true
|
||||
}
|
||||
spID = &id
|
||||
d.kad.EachConn(req.Addr[:], 255, func(p *network.Peer, po int) bool {
|
||||
id := p.ID()
|
||||
|
||||
// get po between chunk and origin
|
||||
if req.Origin.String() == id.String() {
|
||||
originPo = po
|
||||
return false
|
||||
})
|
||||
if sp == nil {
|
||||
return nil, nil, errors.New("no peer found")
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return originPo
|
||||
}
|
||||
|
||||
// FindPeer is returning the closest peer from Kademlia that a chunk
|
||||
// request hasn't already been sent to
|
||||
func (d *Delivery) FindPeer(ctx context.Context, req *storage.Request) (*Peer, error) {
|
||||
var sp *Peer
|
||||
var err error
|
||||
|
||||
osp, _ := ctx.Value("remote.fetch").(opentracing.Span)
|
||||
|
||||
// originPo - proximity of the node that made the request; -1 if the request originator is our node;
|
||||
// myPo - this node's proximity with the requested chunk
|
||||
// selectedPeerPo - kademlia suggested node's proximity with the requested chunk (computed further below)
|
||||
originPo := d.getOriginPo(req)
|
||||
myPo := chunk.Proximity(req.Addr, d.kad.BaseAddr())
|
||||
selectedPeerPo := -1
|
||||
|
||||
depth := d.kad.NeighbourhoodDepth()
|
||||
|
||||
if osp != nil {
|
||||
osp.LogFields(olog.Int("originPo", originPo))
|
||||
osp.LogFields(olog.Int("depth", depth))
|
||||
osp.LogFields(olog.Int("myPo", myPo))
|
||||
}
|
||||
|
||||
// do not forward requests if origin proximity is bigger than our node's proximity
|
||||
// this means that origin is closer to the chunk
|
||||
if originPo > myPo {
|
||||
return nil, errors.New("not forwarding request, origin node is closer to chunk than this node")
|
||||
}
|
||||
|
||||
d.kad.EachConn(req.Addr[:], 255, func(p *network.Peer, po int) bool {
|
||||
id := p.ID()
|
||||
|
||||
// skip light nodes
|
||||
if p.LightNode {
|
||||
return true
|
||||
}
|
||||
|
||||
// do not send request back to peer who asked us. maybe merge with SkipPeer at some point
|
||||
if req.Origin.String() == id.String() {
|
||||
return true
|
||||
}
|
||||
|
||||
// skip peers that we have already tried
|
||||
if req.SkipPeer(id.String()) {
|
||||
log.Trace("findpeer skip peer", "peer", id, "ref", req.Addr.String())
|
||||
return true
|
||||
}
|
||||
|
||||
if myPo < depth { // chunk is NOT within the neighbourhood
|
||||
if po <= myPo { // always choose a peer strictly closer to chunk than us
|
||||
log.Trace("findpeer1a", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
return false
|
||||
} else {
|
||||
log.Trace("findpeer1b", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
}
|
||||
} else { // chunk IS WITHIN neighbourhood
|
||||
if po < depth { // do not select peer outside the neighbourhood. But allows peers further from the chunk than us
|
||||
log.Trace("findpeer2a", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
return false
|
||||
} else if po <= originPo { // avoid loop in neighbourhood, so not forward when a request comes from the neighbourhood
|
||||
log.Trace("findpeer2b", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
return false
|
||||
} else {
|
||||
log.Trace("findpeer2c", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
}
|
||||
}
|
||||
|
||||
// if selected peer is not in the depth (2nd condition; if depth <= po, then peer is in nearest neighbourhood)
|
||||
// and they have a lower po than ours, return error
|
||||
if po < myPo && depth > po {
|
||||
log.Trace("findpeer4 skip peer because origin was closer", "originpo", originPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
|
||||
err = fmt.Errorf("not asking peers further away from origin; ref=%s originpo=%v po=%v depth=%v myPo=%v", req.Addr.String(), originPo, po, depth, myPo)
|
||||
return false
|
||||
}
|
||||
|
||||
// if chunk falls in our nearest neighbourhood (1st condition), but suggested peer is not in
|
||||
// the nearest neighbourhood (2nd condition), don't forward the request to suggested peer
|
||||
if depth <= myPo && depth > po {
|
||||
log.Trace("findpeer5 skip peer because depth", "originpo", originPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
|
||||
err = fmt.Errorf("not going outside of depth; ref=%s originpo=%v po=%v depth=%v myPo=%v", req.Addr.String(), originPo, po, depth, myPo)
|
||||
return false
|
||||
}
|
||||
|
||||
sp = d.getPeer(id)
|
||||
|
||||
// sp could be nil, if we encountered a peer that is not registered for delivery, i.e. doesn't support the `stream` protocol
|
||||
// if sp is not nil, then we have selected the next peer and we stop iterating
|
||||
// if sp is nil, we continue iterating
|
||||
if sp != nil {
|
||||
selectedPeerPo = po
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// continue iterating
|
||||
return true
|
||||
})
|
||||
|
||||
if osp != nil {
|
||||
osp.LogFields(olog.Int("selectedPeerPo", selectedPeerPo))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sp == nil {
|
||||
return nil, errors.New("no peer found")
|
||||
}
|
||||
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
// RequestFromPeers sends a chunk retrieve request to the next found peer
|
||||
func (d *Delivery) RequestFromPeers(ctx context.Context, req *storage.Request, localID enode.ID) (*enode.ID, error) {
|
||||
metrics.GetOrRegisterCounter("delivery.requestfrompeers", nil).Inc(1)
|
||||
|
||||
sp, err := d.FindPeer(ctx, req)
|
||||
if err != nil {
|
||||
log.Trace(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// setting this value in the context creates a new span that can persist across the sendpriority queue and the network roundtrip
|
||||
// this span will finish only when delivery is handled (or times out)
|
||||
ctx = context.WithValue(ctx, tracing.StoreLabelId, "stream.send.request")
|
||||
ctx = context.WithValue(ctx, tracing.StoreLabelMeta, fmt.Sprintf("%v.%v", sp.ID(), req.Addr))
|
||||
log.Trace("request.from.peers", "peer", sp.ID(), "ref", req.Addr)
|
||||
err := sp.SendPriority(ctx, &RetrieveRequestMsg{
|
||||
Addr: req.Addr,
|
||||
SkipCheck: req.SkipCheck,
|
||||
HopCount: req.HopCount,
|
||||
}, Top)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
r := &RetrieveRequestMsg{
|
||||
Addr: req.Addr,
|
||||
}
|
||||
log.Trace("sending retrieve request", "ref", r.Addr, "peer", sp.ID().String(), "origin", localID)
|
||||
err = sp.Send(ctx, r)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
requestFromPeersEachCount.Inc(1)
|
||||
|
||||
return spID, sp.quit, nil
|
||||
spID := sp.ID()
|
||||
return &spID, nil
|
||||
}
|
||||
|
@ -19,26 +19,17 @@ package stream
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||
p2ptest "github.com/ethereum/go-ethereum/p2p/testing"
|
||||
"github.com/ethersphere/swarm/chunk"
|
||||
"github.com/ethersphere/swarm/log"
|
||||
"github.com/ethersphere/swarm/network"
|
||||
pq "github.com/ethersphere/swarm/network/priorityqueue"
|
||||
"github.com/ethersphere/swarm/network/simulation"
|
||||
"github.com/ethersphere/swarm/p2p/protocols"
|
||||
"github.com/ethersphere/swarm/state"
|
||||
"github.com/ethersphere/swarm/storage"
|
||||
"github.com/ethersphere/swarm/testutil"
|
||||
)
|
||||
|
||||
//Test requesting a chunk from a peer then issuing a "empty" OfferedHashesMsg (no hashes available yet)
|
||||
@ -160,18 +151,12 @@ func TestRequestFromPeers(t *testing.T) {
|
||||
streamer: r,
|
||||
}
|
||||
r.setPeer(sp)
|
||||
req := network.NewRequest(
|
||||
storage.Address(hash0[:]),
|
||||
true,
|
||||
&sync.Map{},
|
||||
)
|
||||
ctx := context.Background()
|
||||
id, _, err := delivery.RequestFromPeers(ctx, req)
|
||||
|
||||
req := storage.NewRequest(storage.Address(hash0[:]))
|
||||
id, err := delivery.FindPeer(context.TODO(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if *id != dummyPeerID {
|
||||
if id.ID() != dummyPeerID {
|
||||
t.Fatalf("Expected an id, got %v", id)
|
||||
}
|
||||
}
|
||||
@ -201,15 +186,10 @@ func TestRequestFromPeersWithLightNode(t *testing.T) {
|
||||
}
|
||||
r.setPeer(sp)
|
||||
|
||||
req := network.NewRequest(
|
||||
storage.Address(hash0[:]),
|
||||
true,
|
||||
&sync.Map{},
|
||||
)
|
||||
req := storage.NewRequest(storage.Address(hash0[:]))
|
||||
|
||||
ctx := context.Background()
|
||||
// making a request which should return with "no peer found"
|
||||
_, _, err := delivery.RequestFromPeers(ctx, req)
|
||||
_, err := delivery.FindPeer(context.TODO(), req)
|
||||
|
||||
expectedError := "no peer found"
|
||||
if err.Error() != expectedError {
|
||||
@ -300,293 +280,3 @@ func TestStreamerDownstreamChunkDeliveryMsgExchange(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDeliveryFromNodes(t *testing.T) {
|
||||
testDeliveryFromNodes(t, 2, dataChunkCount, true)
|
||||
testDeliveryFromNodes(t, 2, dataChunkCount, false)
|
||||
testDeliveryFromNodes(t, 4, dataChunkCount, true)
|
||||
testDeliveryFromNodes(t, 4, dataChunkCount, false)
|
||||
|
||||
if testutil.RaceEnabled {
|
||||
// Travis cannot handle more nodes with -race; would time out.
|
||||
return
|
||||
}
|
||||
|
||||
testDeliveryFromNodes(t, 8, dataChunkCount, true)
|
||||
testDeliveryFromNodes(t, 8, dataChunkCount, false)
|
||||
testDeliveryFromNodes(t, 16, dataChunkCount, true)
|
||||
testDeliveryFromNodes(t, 16, dataChunkCount, false)
|
||||
}
|
||||
|
||||
func testDeliveryFromNodes(t *testing.T, nodes, chunkCount int, skipCheck bool) {
|
||||
t.Helper()
|
||||
t.Run(fmt.Sprintf("testDeliveryFromNodes_%d_%d_skipCheck_%t", nodes, chunkCount, skipCheck), func(t *testing.T) {
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||
addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{
|
||||
SkipCheck: skipCheck,
|
||||
Syncing: SyncingDisabled,
|
||||
}, nil)
|
||||
bucket.Store(bucketKeyRegistry, r)
|
||||
|
||||
cleanup = func() {
|
||||
r.Close()
|
||||
clean()
|
||||
}
|
||||
|
||||
return r, cleanup, nil
|
||||
},
|
||||
})
|
||||
defer sim.Close()
|
||||
|
||||
log.Info("Adding nodes to simulation")
|
||||
_, err := sim.AddNodesAndConnectChain(nodes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
log.Info("Starting simulation")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) {
|
||||
nodeIDs := sim.UpNodeIDs()
|
||||
//determine the pivot node to be the first node of the simulation
|
||||
pivot := nodeIDs[0]
|
||||
|
||||
//distribute chunks of a random file into Stores of nodes 1 to nodes
|
||||
//we will do this by creating a file store with an underlying round-robin store:
|
||||
//the file store will create a hash for the uploaded file, but every chunk will be
|
||||
//distributed to different nodes via round-robin scheduling
|
||||
log.Debug("Writing file to round-robin file store")
|
||||
//to do this, we create an array for chunkstores (length minus one, the pivot node)
|
||||
stores := make([]storage.ChunkStore, len(nodeIDs)-1)
|
||||
//we then need to get all stores from the sim....
|
||||
lStores := sim.NodesItems(bucketKeyStore)
|
||||
i := 0
|
||||
//...iterate the buckets...
|
||||
for id, bucketVal := range lStores {
|
||||
//...and remove the one which is the pivot node
|
||||
if id == pivot {
|
||||
continue
|
||||
}
|
||||
//the other ones are added to the array...
|
||||
stores[i] = bucketVal.(storage.ChunkStore)
|
||||
i++
|
||||
}
|
||||
//...which then gets passed to the round-robin file store
|
||||
roundRobinFileStore := storage.NewFileStore(newRoundRobinStore(stores...), storage.NewFileStoreParams(), chunk.NewTags())
|
||||
//now we can actually upload a (random) file to the round-robin store
|
||||
size := chunkCount * chunkSize
|
||||
log.Debug("Storing data to file store")
|
||||
fileHash, wait, err := roundRobinFileStore.Store(ctx, testutil.RandomReader(1, size), int64(size), false)
|
||||
// wait until all chunks stored
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = wait(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//get the pivot node's filestore
|
||||
item, ok := sim.NodeItem(pivot, bucketKeyFileStore)
|
||||
if !ok {
|
||||
return fmt.Errorf("No filestore")
|
||||
}
|
||||
pivotFileStore := item.(*storage.FileStore)
|
||||
log.Debug("Starting retrieval routine")
|
||||
retErrC := make(chan error)
|
||||
go func() {
|
||||
// start the retrieval on the pivot node - this will spawn retrieve requests for missing chunks
|
||||
// we must wait for the peer connections to have started before requesting
|
||||
n, err := readAll(pivotFileStore, fileHash)
|
||||
log.Info(fmt.Sprintf("retrieved %v", fileHash), "read", n, "err", err)
|
||||
retErrC <- err
|
||||
}()
|
||||
|
||||
disconnected := watchDisconnections(ctx, sim)
|
||||
defer func() {
|
||||
if err != nil && disconnected.bool() {
|
||||
err = errors.New("disconnect events received")
|
||||
}
|
||||
}()
|
||||
|
||||
//finally check that the pivot node gets all chunks via the root hash
|
||||
log.Debug("Check retrieval")
|
||||
success := true
|
||||
var total int64
|
||||
total, err = readAll(pivotFileStore, fileHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(fmt.Sprintf("check if %08x is available locally: number of bytes read %v/%v (error: %v)", fileHash, total, size, err))
|
||||
if err != nil || total != int64(size) {
|
||||
success = false
|
||||
}
|
||||
|
||||
if !success {
|
||||
return fmt.Errorf("Test failed, chunks not available on all nodes")
|
||||
}
|
||||
if err := <-retErrC; err != nil {
|
||||
return fmt.Errorf("requesting chunks: %v", err)
|
||||
}
|
||||
log.Debug("Test terminated successfully")
|
||||
return nil
|
||||
})
|
||||
if result.Error != nil {
|
||||
t.Fatal(result.Error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDeliveryFromNodesWithoutCheck(b *testing.B) {
|
||||
for chunks := 32; chunks <= 128; chunks *= 2 {
|
||||
for i := 2; i < 32; i *= 2 {
|
||||
b.Run(
|
||||
fmt.Sprintf("nodes=%v,chunks=%v", i, chunks),
|
||||
func(b *testing.B) {
|
||||
benchmarkDeliveryFromNodes(b, i, chunks, true)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDeliveryFromNodesWithCheck(b *testing.B) {
|
||||
for chunks := 32; chunks <= 128; chunks *= 2 {
|
||||
for i := 2; i < 32; i *= 2 {
|
||||
b.Run(
|
||||
fmt.Sprintf("nodes=%v,chunks=%v", i, chunks),
|
||||
func(b *testing.B) {
|
||||
benchmarkDeliveryFromNodes(b, i, chunks, false)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkDeliveryFromNodes(b *testing.B, nodes, chunkCount int, skipCheck bool) {
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||
addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{
|
||||
SkipCheck: skipCheck,
|
||||
Syncing: SyncingDisabled,
|
||||
SyncUpdateDelay: 0,
|
||||
}, nil)
|
||||
bucket.Store(bucketKeyRegistry, r)
|
||||
|
||||
cleanup = func() {
|
||||
r.Close()
|
||||
clean()
|
||||
}
|
||||
|
||||
return r, cleanup, nil
|
||||
},
|
||||
})
|
||||
defer sim.Close()
|
||||
|
||||
log.Info("Initializing test config")
|
||||
_, err := sim.AddNodesAndConnectChain(nodes)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) {
|
||||
nodeIDs := sim.UpNodeIDs()
|
||||
node := nodeIDs[len(nodeIDs)-1]
|
||||
|
||||
item, ok := sim.NodeItem(node, bucketKeyFileStore)
|
||||
if !ok {
|
||||
return errors.New("No filestore")
|
||||
}
|
||||
remoteFileStore := item.(*storage.FileStore)
|
||||
|
||||
pivotNode := nodeIDs[0]
|
||||
item, ok = sim.NodeItem(pivotNode, bucketKeyNetStore)
|
||||
if !ok {
|
||||
return errors.New("No filestore")
|
||||
}
|
||||
netStore := item.(*storage.NetStore)
|
||||
|
||||
if _, err := sim.WaitTillHealthy(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
disconnected := watchDisconnections(ctx, sim)
|
||||
defer func() {
|
||||
if err != nil && disconnected.bool() {
|
||||
err = errors.New("disconnect events received")
|
||||
}
|
||||
}()
|
||||
// benchmark loop
|
||||
b.ResetTimer()
|
||||
b.StopTimer()
|
||||
Loop:
|
||||
for i := 0; i < b.N; i++ {
|
||||
// uploading chunkCount random chunks to the last node
|
||||
hashes := make([]storage.Address, chunkCount)
|
||||
for i := 0; i < chunkCount; i++ {
|
||||
// create actual size real chunks
|
||||
ctx := context.TODO()
|
||||
hash, wait, err := remoteFileStore.Store(ctx, testutil.RandomReader(i, chunkSize), int64(chunkSize), false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("store: %v", err)
|
||||
}
|
||||
// wait until all chunks stored
|
||||
err = wait(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wait store: %v", err)
|
||||
}
|
||||
// collect the hashes
|
||||
hashes[i] = hash
|
||||
}
|
||||
// now benchmark the actual retrieval
|
||||
// netstore.Get is called for each hash in a go routine and errors are collected
|
||||
b.StartTimer()
|
||||
errs := make(chan error)
|
||||
for _, hash := range hashes {
|
||||
go func(h storage.Address) {
|
||||
_, err := netStore.Get(ctx, chunk.ModeGetRequest, h)
|
||||
log.Warn("test check netstore get", "hash", h, "err", err)
|
||||
errs <- err
|
||||
}(hash)
|
||||
}
|
||||
// count and report retrieval errors
|
||||
// if there are misses then chunk timeout is too low for the distance and volume (?)
|
||||
var total, misses int
|
||||
for err := range errs {
|
||||
if err != nil {
|
||||
log.Warn(err.Error())
|
||||
misses++
|
||||
}
|
||||
total++
|
||||
if total == chunkCount {
|
||||
break
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
|
||||
if misses > 0 {
|
||||
err = fmt.Errorf("%v chunk not found out of %v", misses, total)
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
if result.Error != nil {
|
||||
b.Fatal(result.Error)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||
"github.com/ethersphere/swarm/network/simulation"
|
||||
"github.com/ethersphere/swarm/network/timeouts"
|
||||
"github.com/ethersphere/swarm/state"
|
||||
"github.com/ethersphere/swarm/storage"
|
||||
"github.com/ethersphere/swarm/testutil"
|
||||
@ -58,7 +59,7 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
|
||||
externalStreamSessionAt := uint64(50)
|
||||
externalStreamMaxKeys := uint64(100)
|
||||
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
sim := simulation.NewInProc(map[string]simulation.ServiceFunc{
|
||||
"intervalsStreamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (node.Service, func(), error) {
|
||||
addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket)
|
||||
if err != nil {
|
||||
@ -75,7 +76,7 @@ func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) {
|
||||
return newTestExternalClient(netStore), nil
|
||||
})
|
||||
r.RegisterServerFunc(externalStreamName, func(p *Peer, t string, live bool) (Server, error) {
|
||||
return newTestExternalServer(t, externalStreamSessionAt, externalStreamMaxKeys, nil), nil
|
||||
return newTestExternalServer(t, externalStreamSessionAt, externalStreamMaxKeys), nil
|
||||
})
|
||||
|
||||
cleanup := func() {
|
||||
@ -298,38 +299,42 @@ func newTestExternalClient(netStore *storage.NetStore) *testExternalClient {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *testExternalClient) NeedData(ctx context.Context, hash []byte) func(context.Context) error {
|
||||
wait := c.netStore.FetchFunc(ctx, storage.Address(hash))
|
||||
if wait == nil {
|
||||
return nil
|
||||
func (c *testExternalClient) NeedData(ctx context.Context, key []byte) (bool, func(context.Context) error) {
|
||||
fi, loaded, ok := c.netStore.GetOrCreateFetcher(ctx, key, "syncer")
|
||||
if !ok {
|
||||
return loaded, nil
|
||||
}
|
||||
|
||||
select {
|
||||
case c.hashes <- hash:
|
||||
case c.hashes <- key:
|
||||
case <-ctx.Done():
|
||||
log.Warn("testExternalClient NeedData context", "err", ctx.Err())
|
||||
return func(_ context.Context) error {
|
||||
return false, func(_ context.Context) error {
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
return wait
|
||||
|
||||
return loaded, func(ctx context.Context) error {
|
||||
select {
|
||||
case <-fi.Delivered:
|
||||
case <-time.After(timeouts.SyncerClientWaitTimeout):
|
||||
return fmt.Errorf("chunk not delivered through syncing after %dsec. ref=%s", timeouts.SyncerClientWaitTimeout, fmt.Sprintf("%x", key))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *testExternalClient) Close() {}
|
||||
|
||||
type testExternalServer struct {
|
||||
t string
|
||||
keyFunc func(key []byte, index uint64)
|
||||
sessionAt uint64
|
||||
maxKeys uint64
|
||||
}
|
||||
|
||||
func newTestExternalServer(t string, sessionAt, maxKeys uint64, keyFunc func(key []byte, index uint64)) *testExternalServer {
|
||||
if keyFunc == nil {
|
||||
keyFunc = binary.BigEndian.PutUint64
|
||||
}
|
||||
func newTestExternalServer(t string, sessionAt, maxKeys uint64) *testExternalServer {
|
||||
return &testExternalServer{
|
||||
t: t,
|
||||
keyFunc: keyFunc,
|
||||
sessionAt: sessionAt,
|
||||
maxKeys: maxKeys,
|
||||
}
|
||||
@ -345,7 +350,7 @@ func (s *testExternalServer) SetNextBatch(from uint64, to uint64) ([]byte, uint6
|
||||
}
|
||||
b := make([]byte, HashSize*(to-from+1))
|
||||
for i := from; i <= to; i++ {
|
||||
s.keyFunc(b[(i-from)*HashSize:(i-from+1)*HashSize], i)
|
||||
binary.BigEndian.PutUint64(b[(i-from)*HashSize:(i-from+1)*HashSize], i)
|
||||
}
|
||||
return b, from, to, nil
|
||||
}
|
||||
|
@ -225,9 +225,14 @@ func (p *Peer) handleOfferedHashesMsg(ctx context.Context, req *OfferedHashesMsg
|
||||
for i := 0; i < lenHashes; i += HashSize {
|
||||
hash := hashes[i : i+HashSize]
|
||||
|
||||
if wait := c.NeedData(ctx, hash); wait != nil {
|
||||
log.Trace("checking offered hash", "ref", fmt.Sprintf("%x", hash))
|
||||
|
||||
if _, wait := c.NeedData(ctx, hash); wait != nil {
|
||||
ctr++
|
||||
|
||||
// set the bit, so create a request
|
||||
want.Set(i/HashSize, true)
|
||||
log.Trace("need data", "ref", fmt.Sprintf("%x", hash), "request", true)
|
||||
|
||||
// measure how long it takes before we mark chunks for retrieval, and actually send the request
|
||||
if !wantDelaySet {
|
||||
|
@ -30,11 +30,9 @@ import (
|
||||
"github.com/ethersphere/swarm/network"
|
||||
pq "github.com/ethersphere/swarm/network/priorityqueue"
|
||||
"github.com/ethersphere/swarm/network/stream/intervals"
|
||||
"github.com/ethersphere/swarm/spancontext"
|
||||
"github.com/ethersphere/swarm/state"
|
||||
"github.com/ethersphere/swarm/storage"
|
||||
"github.com/ethersphere/swarm/tracing"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
type notFoundError struct {
|
||||
@ -174,13 +172,6 @@ func (p *Peer) SendPriority(ctx context.Context, msg interface{}, priority uint8
|
||||
|
||||
// SendOfferedHashes sends OfferedHashesMsg protocol msg
|
||||
func (p *Peer) SendOfferedHashes(s *server, f, t uint64) error {
|
||||
var sp opentracing.Span
|
||||
ctx, sp := spancontext.StartSpan(
|
||||
context.TODO(),
|
||||
"send.offered.hashes",
|
||||
)
|
||||
defer sp.Finish()
|
||||
|
||||
defer metrics.GetOrRegisterResettingTimer("send.offered.hashes", nil).UpdateSince(time.Now())
|
||||
|
||||
hashes, from, to, err := s.setNextBatch(f, t)
|
||||
@ -199,8 +190,7 @@ func (p *Peer) SendOfferedHashes(s *server, f, t uint64) error {
|
||||
Stream: s.stream,
|
||||
}
|
||||
log.Trace("Swarm syncer offer batch", "peer", p.ID(), "stream", s.stream, "len", len(hashes), "from", from, "to", to)
|
||||
ctx = context.WithValue(ctx, "stream_send_tag", "send.offered.hashes")
|
||||
return p.SendPriority(ctx, msg, s.priority)
|
||||
return p.SendPriority(context.TODO(), msg, s.priority)
|
||||
}
|
||||
|
||||
func (p *Peer) getServer(s Stream) (*server, error) {
|
||||
|
@ -130,7 +130,7 @@ func TestSyncSubscriptionsDiff(t *testing.T) {
|
||||
// made on initial node connections and that subscriptions are correctly changed
|
||||
// when kademlia neighbourhood depth is changed by connecting more nodes.
|
||||
func TestUpdateSyncingSubscriptions(t *testing.T) {
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
sim := simulation.NewInProc(map[string]simulation.ServiceFunc{
|
||||
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||
addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket)
|
||||
if err != nil {
|
||||
|
@ -171,7 +171,7 @@ func runPureRetrievalTest(t *testing.T, nodeCount int, chunkCount int) {
|
||||
t.Helper()
|
||||
// the pure retrieval test needs a different service map, as we want
|
||||
// syncing disabled and we don't need to set the syncUpdateDelay
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
sim := simulation.NewInProc(map[string]simulation.ServiceFunc{
|
||||
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||
addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket)
|
||||
if err != nil {
|
||||
@ -316,7 +316,7 @@ func runFileRetrievalTest(t *testing.T, nodeCount int) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
sim := simulation.New(retrievalSimServiceMap)
|
||||
sim := simulation.NewInProc(retrievalSimServiceMap)
|
||||
defer sim.Close()
|
||||
|
||||
log.Info("Initializing test config", "node count", nodeCount)
|
||||
@ -404,7 +404,7 @@ func runRetrievalTest(t *testing.T, chunkCount int, nodeCount int) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
sim := simulation.New(retrievalSimServiceMap)
|
||||
sim := simulation.NewInProc(retrievalSimServiceMap)
|
||||
defer sim.Close()
|
||||
|
||||
conf := &synctestConfig{}
|
||||
|
@ -63,8 +63,8 @@ const (
|
||||
|
||||
// Tests in this file should not request chunks from peers.
|
||||
// This function will panic indicating that there is a problem if request has been made.
|
||||
func dummyRequestFromPeers(_ context.Context, req *network.Request) (*enode.ID, chan struct{}, error) {
|
||||
panic(fmt.Sprintf("unexpected request: address %s, source %s", req.Addr.String(), req.Source.String()))
|
||||
func dummyRequestFromPeers(_ context.Context, req *storage.Request, _ enode.ID) (*enode.ID, error) {
|
||||
panic(fmt.Sprintf("unexpected request: address %s", req.Addr.String()))
|
||||
}
|
||||
|
||||
//This test is a syncing test for nodes.
|
||||
@ -134,7 +134,7 @@ var simServiceMap = map[string]simulation.ServiceFunc{
|
||||
}
|
||||
|
||||
func testSyncingViaGlobalSync(t *testing.T, chunkCount int, nodeCount int) {
|
||||
sim := simulation.New(simServiceMap)
|
||||
sim := simulation.NewInProc(simServiceMap)
|
||||
defer sim.Close()
|
||||
|
||||
log.Info("Initializing test config")
|
||||
|
@ -543,7 +543,7 @@ func (c *client) NextInterval() (start, end uint64, err error) {
|
||||
|
||||
// Client interface for incoming peer Streamer
|
||||
type Client interface {
|
||||
NeedData(context.Context, []byte) func(context.Context) error
|
||||
NeedData(context.Context, []byte) (bool, func(context.Context) error)
|
||||
Close()
|
||||
}
|
||||
|
||||
@ -623,7 +623,7 @@ func (r *Registry) createSpec() {
|
||||
// Spec is the spec of the streamer protocol
|
||||
var spec = &protocols.Spec{
|
||||
Name: "stream",
|
||||
Version: 9,
|
||||
Version: 10,
|
||||
MaxMsgSize: 10 * 1024 * 1024,
|
||||
Messages: []interface{}{
|
||||
UnsubscribeMsg{},
|
||||
|
@ -93,20 +93,20 @@ func newTestClient(t string) *testClient {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *testClient) NeedData(ctx context.Context, hash []byte) func(context.Context) error {
|
||||
func (self *testClient) NeedData(ctx context.Context, hash []byte) (bool, func(context.Context) error) {
|
||||
self.receivedHashes[string(hash)] = hash
|
||||
if bytes.Equal(hash, hash0[:]) {
|
||||
return func(context.Context) error {
|
||||
return false, func(context.Context) error {
|
||||
<-self.wait0
|
||||
return nil
|
||||
}
|
||||
} else if bytes.Equal(hash, hash2[:]) {
|
||||
return func(context.Context) error {
|
||||
return false, func(context.Context) error {
|
||||
<-self.wait2
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (self *testClient) Close() {}
|
||||
@ -998,7 +998,7 @@ func TestGetServerSubscriptionsRPC(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
// create a standard sim
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
sim := simulation.NewInProc(map[string]simulation.ServiceFunc{
|
||||
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||
addr, netStore, delivery, clean, err := newNetStoreAndDeliveryWithRequestFunc(ctx, bucket, dummyRequestFromPeers)
|
||||
if err != nil {
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethersphere/swarm/chunk"
|
||||
"github.com/ethersphere/swarm/log"
|
||||
"github.com/ethersphere/swarm/network/timeouts"
|
||||
"github.com/ethersphere/swarm/storage"
|
||||
)
|
||||
|
||||
@ -73,7 +74,7 @@ func (s *SwarmSyncerServer) Close() {
|
||||
|
||||
// GetData retrieves the actual chunk from netstore
|
||||
func (s *SwarmSyncerServer) GetData(ctx context.Context, key []byte) ([]byte, error) {
|
||||
ch, err := s.netStore.Get(ctx, chunk.ModeGetSync, storage.Address(key))
|
||||
ch, err := s.netStore.Store.Get(ctx, chunk.ModeGetSync, storage.Address(key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -198,9 +199,24 @@ func RegisterSwarmSyncerClient(streamer *Registry, netStore *storage.NetStore) {
|
||||
})
|
||||
}
|
||||
|
||||
// NeedData
|
||||
func (s *SwarmSyncerClient) NeedData(ctx context.Context, key []byte) (wait func(context.Context) error) {
|
||||
return s.netStore.FetchFunc(ctx, key)
|
||||
func (s *SwarmSyncerClient) NeedData(ctx context.Context, key []byte) (loaded bool, wait func(context.Context) error) {
|
||||
start := time.Now()
|
||||
|
||||
fi, loaded, ok := s.netStore.GetOrCreateFetcher(ctx, key, "syncer")
|
||||
if !ok {
|
||||
return loaded, nil
|
||||
}
|
||||
|
||||
return loaded, func(ctx context.Context) error {
|
||||
select {
|
||||
case <-fi.Delivered:
|
||||
metrics.GetOrRegisterResettingTimer(fmt.Sprintf("fetcher.%s.syncer", fi.CreatedBy), nil).UpdateSince(start)
|
||||
case <-time.After(timeouts.SyncerClientWaitTimeout):
|
||||
metrics.GetOrRegisterCounter("fetcher.syncer.timeout", nil).Inc(1)
|
||||
return fmt.Errorf("chunk not delivered through syncing after %dsec. ref=%s", timeouts.SyncerClientWaitTimeout, fmt.Sprintf("%x", key))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SwarmSyncerClient) Close() {}
|
||||
|
@ -54,7 +54,7 @@ func TestTwoNodesFullSync(t *testing.T) { //
|
||||
chunkCount = 1000 //~4mb
|
||||
syncTime = 5 * time.Second
|
||||
)
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
sim := simulation.NewInProc(map[string]simulation.ServiceFunc{
|
||||
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||
addr := network.NewAddr(ctx.Config.Node())
|
||||
|
||||
@ -253,7 +253,7 @@ func TestStarNetworkSync(t *testing.T) {
|
||||
syncTime = 30 * time.Second
|
||||
filesize = chunkCount * chunkSize
|
||||
)
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
sim := simulation.NewInProc(map[string]simulation.ServiceFunc{
|
||||
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||
addr := network.NewAddr(ctx.Config.Node())
|
||||
|
||||
@ -466,7 +466,7 @@ type chunkProxData struct {
|
||||
func TestSameVersionID(t *testing.T) {
|
||||
//test version ID
|
||||
v := uint(1)
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
sim := simulation.NewInProc(map[string]simulation.ServiceFunc{
|
||||
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||
addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket)
|
||||
if err != nil {
|
||||
@ -529,7 +529,7 @@ func TestSameVersionID(t *testing.T) {
|
||||
func TestDifferentVersionID(t *testing.T) {
|
||||
//create a variable to hold the version ID
|
||||
v := uint(0)
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
sim := simulation.NewInProc(map[string]simulation.ServiceFunc{
|
||||
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||
addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket)
|
||||
if err != nil {
|
||||
|
24
network/timeouts/timeouts.go
Normal file
@ -0,0 +1,24 @@
|
||||
package timeouts
|
||||
|
||||
import "time"
|
||||
|
||||
// FailedPeerSkipDelay is the time we consider a peer to be skipped for a particular request/chunk,
|
||||
// because this peer failed to deliver it during the SearchTimeout interval
|
||||
var FailedPeerSkipDelay = 20 * time.Second
|
||||
|
||||
// FetcherGlobalTimeout is the max time a node tries to find a chunk for a client, after which it returns a 404
|
||||
// Basically this is the amount of time a singleflight request for a given chunk lives
|
||||
var FetcherGlobalTimeout = 10 * time.Second
|
||||
|
||||
// SearchTimeout is the max time requests wait for a peer to deliver a chunk, after which another peer is tried
|
||||
var SearchTimeout = 500 * time.Millisecond
|
||||
|
||||
// SyncerClientWaitTimeout is the max time a syncer client waits for a chunk to be delivered during syncing
|
||||
var SyncerClientWaitTimeout = 20 * time.Second
|
||||
|
||||
// Within handleOfferedHashesMsg - how long to wait for a given batch of chunks to be delivered by the peer offering them
|
||||
var SyncBatchTimeout = 10 * time.Second
|
||||
|
||||
// Within SwarmSyncerServer - If at least one chunk is added to the batch and no new chunks
|
||||
// are added in BatchTimeout period, the batch will be returned.
|
||||
var BatchTimeout = 2 * time.Second
|
@ -292,7 +292,7 @@ func testSwarmNetwork(t *testing.T, o *testSwarmNetworkOptions, steps ...testSwa
|
||||
o = new(testSwarmNetworkOptions)
|
||||
}
|
||||
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
sim := simulation.NewInProc(map[string]simulation.ServiceFunc{
|
||||
"swarm": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||
config := api.NewConfig()
|
||||
|
||||
|
@ -227,7 +227,7 @@ func testProxNetwork(t *testing.T, nodeCount int, msgCount int, timeout time.Dur
|
||||
handlerContextFuncs := make(map[Topic]handlerContextFunc)
|
||||
handlerContextFuncs[topic] = nodeMsgHandler
|
||||
services := newProxServices(td, true, handlerContextFuncs, td.kademlias)
|
||||
td.sim = simulation.New(services)
|
||||
td.sim = simulation.NewInProc(services)
|
||||
defer td.sim.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
@ -238,7 +238,7 @@ func TestRandomData(t *testing.T) {
|
||||
// This test can validate files up to a relatively short length, as tree chunker slows down drastically.
|
||||
// Validation of longer files is done by TestLocalStoreAndRetrieve in swarm package.
|
||||
//sizes := []int{1, 60, 83, 179, 253, 1024, 4095, 4096, 4097, 8191, 8192, 8193, 12287, 12288, 12289, 524288, 524288 + 1, 524288 + 4097, 7 * 524288, 7*524288 + 1, 7*524288 + 4097}
|
||||
sizes := []int{1, 60, 83, 179, 253, 1024, 4095, 4097, 8191, 8192, 12288, 12289, 524288}
|
||||
sizes := []int{1, 60, 83, 179, 253, 1024, 4095, 4097, 8191, 8192, 12288, 12289, 524288, 2345678}
|
||||
tester := &chunkerTester{t: t}
|
||||
|
||||
for _, s := range sizes {
|
||||
|